diff --git a/.gitignore b/.gitignore index 27876b2..b4cc46b 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,7 @@ target/ flip.rs .vscode rust-analyzer.json +rust-analyzer.toml data/ src/gpu/ src/tests/ diff --git a/Cargo.toml b/Cargo.toml index 909fe3f..b3a476b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,11 +6,12 @@ edition = "2024" [features] default = [] use_f64 = [] -use_gpu = [] -use_nvtx = [] -cuda = ["dep:cudarc", "dep:cust", "dep:cust_raw"] +use_gpu = ["dep:wgpu"] +use_nvtx = ["dep:nvtx"] +cuda = ["dep:cudarc", "dep:cust", "dep:cust_raw", "dep:cuda-runtime-sys"] vulkan = ["ash", "gpu-allocator"] ash = ["dep:ash"] +gpu-allocator = ["dep:gpu-allocator"] [dependencies] anyhow = "1.0.100" @@ -22,7 +23,6 @@ indicatif = "0.18.3" lazy_static = "1.5.0" log = "0.4.29" memmap2 = "0.9.9" -nvtx = "1.3.0" parking_lot = "0.12.5" paste = "1.0.15" qoi = "0.4.1" @@ -30,16 +30,6 @@ rand = "0.9.2" rayon = "1.11.0" thiserror = "2.0.17" unicode-normalization = "0.1.25" -wgpu = "27.0.1" - -shared = { path = "shared" } -ptex-filter = { path = "crates/ptex-filter" } -# kernels = { path = "kernels" } -ash = { version = "0.38", optional = true } -gpu-allocator = { version = "0.28", features = ["vulkan"], optional = true } -cuda_std = { git = "https://github.com/Rust-GPU/Rust-CUDA", branch = "main", default-features = false, optional = true } -cust = { git = "https://github.com/Rust-GPU/Rust-CUDA", branch = "main", default-features = false, features = ["glam"], optional = true } -cust_raw = { git = "https://github.com/Rust-GPU/Rust-CUDA", branch = "main", default-features = false, optional = true } ptex = "0.3.0" slice = "0.0.4" crossbeam-channel = "0.5.15" @@ -49,7 +39,18 @@ enum_dispatch = "0.3.13" bytemuck = "1.24.0" once_cell = "1.21.3" smallvec = "1.15.1" -cuda-runtime-sys = "0.3.0-alpha.1" + +shared = { path = "shared" } +ptex-filter = { path = "crates/ptex-filter" } +# kernels = { path = "kernels" } +nvtx = { version = "1.3.0", optional = true } +wgpu = { version = "27.0.1", optional = true } +ash = { version = "0.38", optional = true } +gpu-allocator = { version = "0.28", features = ["vulkan"], optional = true } +cuda_std = { git = "https://github.com/Rust-GPU/Rust-CUDA", branch = "main", default-features = false, optional = true } +cust = { git = "https://github.com/Rust-GPU/Rust-CUDA", branch = "main", default-features = false, features = ["glam"], optional = true } +cust_raw = { git = "https://github.com/Rust-GPU/Rust-CUDA", branch = "main", default-features = false, optional = true } +cuda-runtime-sys = { version = "0.3.0-alpha.1", optional = true} cudarc = { version = "0.18.2", features = ["cuda-13000"], optional = true } [build-dependencies] @@ -59,7 +60,7 @@ cc = "1.2.53" [workspace] members = ["shared"] -exclude = ["crates/ptex-filter"] +exclude = ["crates/ptex-filter", "kernels"] [lints.clippy] excessive_precision = "allow" diff --git a/kernels/Cargo.toml b/kernels/Cargo.toml index e184d53..b65cb5c 100644 --- a/kernels/Cargo.toml +++ b/kernels/Cargo.toml @@ -1,11 +1,18 @@ [package] name = "kernels" version = "0.1.0" -edition = "2024" - -[dependencies] -cuda_std = { git = "https://github.com/rust-gpu/rust-cuda", rev = "7fa76f3d717038a92c90bf4a482b0b8dd3259344" } -shared = { path = "../shared", features = ["cuda"] } +edition = "2021" [lib] -crate-type = ["cdylib", "rlib"] +crate-type = ["dylib"] + +[dependencies] +spirv-std = { git = "https://github.com/Rust-GPU/rust-gpu", branch = "main" } +shared = { path = "../shared" } + +[package.metadata.rust-gpu.install] +spirv-builder-source = "https://github.com/Rust-GPU/rust-gpu" + +[package.metadata.rust-gpu.build] +target = "spirv-unknown-vulkan1.2" + diff --git a/kernels/src/lib.rs b/kernels/src/lib.rs index 409331f..8097b7d 100644 --- a/kernels/src/lib.rs +++ b/kernels/src/lib.rs @@ -1,30 +1,20 @@ -#![cfg_attr(target_arch = "nvptx64", no_std)] -#![cfg_attr(target_arch = "nvptx64", feature(abi_ptx))] +#![cfg_attr(target_arch = "spirv", no_std)] -use cuda_std::prelude::*; +use spirv_std::spirv; -/// Scales each element: data[i] *= scale -#[kernel] -#[allow(improper_ctypes_definitions)] -pub unsafe fn scale_array(data: *mut f32, len: u32, scale: f32) { - let idx = thread::index_1d() as u32; - if idx >= len { - return; +/// Core logic — testable on CPU +pub fn scale_kernel_logic(idx: usize, input: &[f32], output: &mut [f32], scale: f32) { + if idx < input.len() { + output[idx] = input[idx] * scale; } - - let ptr = unsafe { data.add(idx as usize) }; - *ptr = *ptr * scale; } -/// Adds two arrays: c[i] = a[i] + b[i] -#[kernel] -#[allow(improper_ctypes_definitions)] -pub unsafe fn add_arrays(a: *const f32, b: *const f32, c: *mut f32, len: u32) { - let idx = thread::index_1d() as u32; - if idx >= len { - return; - } - - let i = idx as usize; - *c.add(i) = *a.add(i) + *b.add(i); +#[spirv(compute(threads(64)))] +pub fn scale_kernel( + #[spirv(global_invocation_id)] id: spirv_std::glam::UVec3, + #[spirv(storage_buffer, descriptor_set = 0, binding = 0)] input: &[f32], + #[spirv(storage_buffer, descriptor_set = 0, binding = 1)] output: &mut [f32], + #[spirv(push_constant)] scale: &f32, +) { + scale_kernel_logic(id.x as usize, input, output, *scale); } diff --git a/shared/Cargo.toml b/shared/Cargo.toml index 4066c28..8db17c2 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -5,20 +5,12 @@ edition = "2024" [dependencies] bitflags = "2.10.0" -bumpalo = "3.19.1" bytemuck = { version = "1.24.0", features = ["derive"] } enum_dispatch = "0.3.13" -log = "0.4.29" -num = "0.4.3" -num-integer = "0.1.46" -num-traits = "0.2.19" -once_cell = "1.21.3" -smallvec = "1.15.1" +num-traits = { version = "0.2.19", default-features = false, features = ["libm"] } cuda_std = { git = "https://github.com/Rust-GPU/Rust-CUDA", branch = "main", default-features = false, optional = true } -half = "2.7.1" -rand = "0.9.2" -anyhow = "1.0.100" [features] use_f64 = [] cuda = ["cuda_std"] +cpu_debug = [] diff --git a/shared/src/bxdfs/complex.rs b/shared/src/bxdfs/complex.rs index f091941..8a178fd 100644 --- a/shared/src/bxdfs/complex.rs +++ b/shared/src/bxdfs/complex.rs @@ -18,6 +18,7 @@ use crate::utils::sampling::{ }; use crate::{Float, INV_2_PI, INV_PI, PI}; use core::any::Any; +use num_traits::Float as NumFloat; static P_MAX: usize = 3; #[repr(C)] @@ -81,7 +82,7 @@ impl HairBxDF { let ap0 = SampledSpectrum::new(f); let ap1 = t * (1.0 - f).powi(2); let tf = t * f; - std::array::from_fn(|p| match p { + core::array::from_fn(|p| match p { 0 => ap0, 1 => ap1, _ if p < P_MAX => ap1 * tf.pow_int(p - 1), @@ -133,7 +134,7 @@ impl HairBxDF { let t = t_value.exp(); let ap = Self::ap(cos_theta_o, self.eta, self.h, t); let sum_y: Float = ap.iter().map(|s| s.average()).sum(); - std::array::from_fn(|i| ap[i].average() / sum_y) + core::array::from_fn(|i| ap[i].average() / sum_y) } pub fn sigma_a_from_concentration( diff --git a/shared/src/bxdfs/conductor.rs b/shared/src/bxdfs/conductor.rs index 6db1e8b..167d4e9 100644 --- a/shared/src/bxdfs/conductor.rs +++ b/shared/src/bxdfs/conductor.rs @@ -8,6 +8,7 @@ use crate::spectra::SampledSpectrum; use crate::utils::sampling::{cosine_hemisphere_pdf, sample_cosine_hemisphere}; use crate::{Float, INV_PI}; use core::any::Any; +use num_traits::Float as NumFloat; #[repr(C)] #[derive(Debug, Clone, Copy)] diff --git a/shared/src/bxdfs/dielectric.rs b/shared/src/bxdfs/dielectric.rs index d5b3d9c..8a8c7b5 100644 --- a/shared/src/bxdfs/dielectric.rs +++ b/shared/src/bxdfs/dielectric.rs @@ -11,6 +11,7 @@ use crate::utils::math::square; use crate::utils::sampling::{cosine_hemisphere_pdf, sample_cosine_hemisphere}; use crate::{Float, INV_PI}; use core::any::Any; +use num_traits::Float as NumFloat; #[repr(C)] #[derive(Debug, Copy, Clone)] diff --git a/shared/src/bxdfs/layered.rs b/shared/src/bxdfs/layered.rs index 65e9c37..7af305b 100644 --- a/shared/src/bxdfs/layered.rs +++ b/shared/src/bxdfs/layered.rs @@ -9,7 +9,6 @@ use crate::core::geometry::{ spherical_direction, spherical_theta, }; use crate::core::medium::{HGPhaseFunction, PhaseFunctionTrait}; -use crate::core::options::get_options; use crate::core::scattering::{ TrowbridgeReitzDistribution, fr_complex, fr_complex_from_spectrum, fr_dielectric, reflect, refract, @@ -30,6 +29,7 @@ use crate::utils::sampling::{ }; use crate::{Float, INV_2_PI, INV_4_PI, INV_PI, ONE_MINUS_EPSILON, PI, PI_OVER_2}; use core::any::Any; +use num_traits::Float as NumFloat; #[derive(Copy, Clone)] pub enum TopOrBottom<'a, T, B> { @@ -91,6 +91,7 @@ where albedo: SampledSpectrum, max_depth: u32, n_samples: u32, + seed: i32, } impl LayeredBxDF @@ -106,6 +107,7 @@ where g: Float, max_depth: u32, n_samples: u32, + seed: i32, ) -> Self { Self { top, @@ -115,6 +117,7 @@ where albedo, max_depth, n_samples, + seed, } } @@ -369,7 +372,7 @@ where f = self.n_samples as Float * enter_interface.f(wo, wi, mode); } - let hash0 = hash_buffer(&[get_options().seed as Float, wo.x(), wo.y(), wo.z()], 0); + let hash0 = hash_buffer(&[self.seed as Float, wo.x(), wo.y(), wo.z()], 0); let hash1 = hash_buffer(&[wi.x(), wi.y(), wi.z()], 0); let mut rng = Rng::new_with_offset(hash0, hash1); @@ -415,7 +418,7 @@ where let mut specular_path = bs.is_specular(); // Declare RNG for layered BSDF sampling - let hash0 = hash_buffer(&[get_options().seed as Float, wo.x(), wo.y(), wo.z()], 0); + let hash0 = hash_buffer(&[self.seed as Float, wo.x(), wo.y(), wo.z()], 0); let hash1 = hash_buffer(&[uc, u.x(), u.y()], 0); let mut rng = Rng::new_with_offset(hash0, hash1); @@ -517,7 +520,7 @@ where wi = -wi; } - let hash0 = hash_buffer(&[get_options().seed as Float, wi.x(), wi.y(), wi.z()], 0); + let hash0 = hash_buffer(&[self.seed as Float, wi.x(), wi.y(), wi.z()], 0); let hash1 = hash_buffer(&[wo.x(), wo.y(), wo.z()], 0); let mut rng = Rng::new_with_offset(hash0, hash1); diff --git a/shared/src/bxdfs/measured.rs b/shared/src/bxdfs/measured.rs index 2eae9af..ce1ae1f 100644 --- a/shared/src/bxdfs/measured.rs +++ b/shared/src/bxdfs/measured.rs @@ -11,6 +11,7 @@ use crate::utils::ptr::Ptr; use crate::utils::sampling::{PiecewiseLinear2D, cosine_hemisphere_pdf, sample_cosine_hemisphere}; use crate::{Float, INV_PI, PI, PI_OVER_2}; use core::any::Any; +use num_traits::Float as NumFloat; #[repr(C)] #[derive(Debug, Copy, Clone)] diff --git a/shared/src/cameras/realistic.rs b/shared/src/cameras/realistic.rs index 31957a6..9f900b9 100644 --- a/shared/src/cameras/realistic.rs +++ b/shared/src/cameras/realistic.rs @@ -13,6 +13,7 @@ use crate::core::scattering::refract; use crate::spectra::{SampledSpectrum, SampledWavelengths}; use crate::utils::Ptr; use crate::utils::math::{lerp, quadratic, square}; +use num_traits::Float as NumFloat; #[repr(C)] #[derive(Debug, Copy, Clone)] @@ -35,87 +36,17 @@ const EXIT_PUPIL_SAMPLES: usize = 64; #[repr(C)] #[derive(Debug, Copy, Clone)] pub struct RealisticCamera { - base: CameraBase, - focus_distance: Float, - set_aperture_diameter: Float, - aperture_image: Ptr, - element_interfaces: Ptr, - n_elements: usize, - physical_extent: Bounds2f, - exit_pupil_bounds: [Bounds2f; EXIT_PUPIL_SAMPLES], + pub base: CameraBase, + pub focus_distance: Float, + pub set_aperture_diameter: Float, + pub aperture_image: Ptr, + pub element_interfaces: Ptr, + pub n_elements: usize, + pub physical_extent: Bounds2f, + pub exit_pupil_bounds: [Bounds2f; EXIT_PUPIL_SAMPLES], } -#[cfg(not(target_os = "cuda"))] impl RealisticCamera { - pub fn new( - base: CameraBase, - lens_params: &[Float], - focus_distance: Float, - set_aperture_diameter: Float, - aperture_image: Ptr, - ) -> Self { - let film_ptr = base.film; - if film_ptr.is_null() { - panic!("Camera must have a film"); - } - let film = &*film_ptr; - - let aspect = film.full_resolution().x() as Float / film.full_resolution().y() as Float; - let diagonal = film.diagonal(); - let x = (square(diagonal) / (1.0 + square(diagonal))).sqrt(); - let y = x * aspect; - let physical_extent = - Bounds2f::from_points(Point2f::new(-x / 2., -y / 2.), Point2f::new(x / 2., y / 2.)); - let mut element_interface: Vec = Vec::new(); - - for i in (0..lens_params.len()).step_by(4) { - let curvature_radius = lens_params[i] / 1000.0; - let thickness = lens_params[i + 1] / 1000.0; - let eta = lens_params[i + 2]; - let mut aperture_diameter = lens_params[i + 3] / 1000.0; - - if curvature_radius == 0.0 { - aperture_diameter /= 1000.0; - if set_aperture_diameter > aperture_diameter { - println!("Aperture is larger than possible") - } else { - aperture_diameter = set_aperture_diameter; - } - } - let el_int = LensElementInterface { - curvature_radius, - thickness, - eta, - aperture_radius: aperture_diameter / 2.0, - }; - element_interface.push(el_int); - } - - let half_diag = film.diagonal() / 2.0; - let mut exit_pupil_bounds = [Bounds2f::default(); EXIT_PUPIL_SAMPLES]; - - for i in 0..EXIT_PUPIL_SAMPLES { - let r0 = (i as Float / EXIT_PUPIL_SAMPLES as Float) * half_diag; - let r1 = ((i + 1) as Float / EXIT_PUPIL_SAMPLES as Float) * half_diag; - exit_pupil_bounds[i] = Self::compute_exit_pupil_bounds(&element_interface, r0, r1); - } - - let n_elements = element_interface.len(); - let element_interfaces = element_interface.as_ptr(); - std::mem::forget(element_interface); - - Self { - base, - focus_distance, - element_interfaces: Ptr::from(element_interfaces), - n_elements, - physical_extent, - set_aperture_diameter, - aperture_image, - exit_pupil_bounds, - } - } - pub fn compute_cardinal_points(r_in: Ray, r_out: Ray) -> (Float, Float) { let tf = -r_out.o.x() / r_out.d.x(); let tp = (r_in.o.x() - r_out.o.x()) / r_out.d.x(); @@ -218,11 +149,8 @@ impl RealisticCamera { } } + // Unable to find exit pupil in x = {},{} on film. if pupil_bounds.is_degenerate() { - print!( - "Unable to find exit pupil in x = {},{} on film.", - film_x_0, film_x_1 - ); return pupil_bounds; } diff --git a/shared/src/cameras/spherical.rs b/shared/src/cameras/spherical.rs index 8c97a91..467f0b8 100644 --- a/shared/src/cameras/spherical.rs +++ b/shared/src/cameras/spherical.rs @@ -6,6 +6,7 @@ use crate::core::pbrt::{Float, PI}; use crate::core::sampler::CameraSample; use crate::spectra::{SampledSpectrum, SampledWavelengths}; use crate::utils::math::{equal_area_square_to_sphere, wrap_equal_area_square}; +use num_traits::Float as NumFloat; #[repr(C)] #[derive(Debug, Copy, Clone, PartialEq)] diff --git a/shared/src/core/bssrdf.rs b/shared/src/core/bssrdf.rs index 9ecd28b..c977d9c 100644 --- a/shared/src/core/bssrdf.rs +++ b/shared/src/core/bssrdf.rs @@ -9,7 +9,7 @@ use crate::utils::math::{catmull_rom_weights, square}; use crate::utils::sampling::sample_catmull_rom_2d; use crate::{Float, PI}; use enum_dispatch::enum_dispatch; -use std::sync::Arc; +use num_traits::Float as NumFloat; #[derive(Debug)] pub struct BSSRDFSample { diff --git a/shared/src/core/camera.rs b/shared/src/core/camera.rs index 66acae6..a7e052a 100644 --- a/shared/src/core/camera.rs +++ b/shared/src/core/camera.rs @@ -5,15 +5,15 @@ use crate::core::geometry::{ }; use crate::core::interaction::Interaction; use crate::core::medium::Medium; -use crate::core::options::RenderingCoordinateSystem; use crate::core::pbrt::Float; use crate::core::sampler::CameraSample; use crate::spectra::{SampledSpectrum, SampledWavelengths}; use crate::utils::math::lerp; +use crate::utils::options::RenderingCoordinateSystem; use crate::utils::ptr::Ptr; use crate::utils::transform::{AnimatedTransform, Transform}; - use enum_dispatch::enum_dispatch; +use num_traits::Float as NumFloat; #[repr(C)] #[derive(Debug, Clone, Copy)] diff --git a/shared/src/core/color.rs b/shared/src/core/color.rs index 508ed2a..cd9ee9c 100644 --- a/shared/src/core/color.rs +++ b/shared/src/core/color.rs @@ -1,6 +1,6 @@ -use std::any::TypeId; -use std::fmt; -use std::ops::{ +use core::any::TypeId; +use core::fmt; +use core::ops::{ Add, AddAssign, Div, DivAssign, Index, IndexMut, Mul, MulAssign, Neg, Sub, SubAssign, }; @@ -10,6 +10,7 @@ use crate::core::spectrum::Spectrum; use crate::utils::Ptr; use crate::utils::find_interval; use crate::utils::math::{SquareMatrix, SquareMatrix3f, clamp, evaluate_polynomial, lerp}; +use num_traits::Float as NumFloat; use enum_dispatch::enum_dispatch; @@ -35,7 +36,7 @@ impl From<[Float; 3]> for XYZ { impl<'a> IntoIterator for &'a XYZ { type Item = &'a Float; - type IntoIter = std::array::IntoIter<&'a Float, 3>; + type IntoIter = core::array::IntoIter<&'a Float, 3>; fn into_iter(self) -> Self::IntoIter { [&self.x, &self.y, &self.z].into_iter() @@ -284,7 +285,7 @@ impl From<(Float, Float, Float)> for RGB { impl<'a> IntoIterator for &'a RGB { type Item = &'a Float; - type IntoIter = std::array::IntoIter<&'a Float, 3>; + type IntoIter = core::array::IntoIter<&'a Float, 3>; fn into_iter(self) -> Self::IntoIter { [&self.r, &self.g, &self.b].into_iter() diff --git a/shared/src/core/film.rs b/shared/src/core/film.rs index 5d9c3c2..8c9d807 100644 --- a/shared/src/core/film.rs +++ b/shared/src/core/film.rs @@ -19,6 +19,7 @@ use crate::utils::math::{SquareMatrix, wrap_equal_area_square}; use crate::utils::sampling::VarianceEstimator; use crate::utils::transform::AnimatedTransform; use crate::utils::{AtomicFloat, Ptr}; +use num_traits::Float as NumFloat; #[repr(C)] #[derive(Debug, Clone)] @@ -298,9 +299,9 @@ pub struct SpectralPixel { impl Clone for SpectralPixel { fn clone(&self) -> Self { Self { - rgb_sum: std::array::from_fn(|i| AtomicFloat::new(self.rgb_sum[i].get())), + rgb_sum: core::array::from_fn(|i| AtomicFloat::new(self.rgb_sum[i].get())), rgb_weight_sum: AtomicFloat::new(self.rgb_weight_sum.get()), - rgb_splat: std::array::from_fn(|i| AtomicFloat::new(self.rgb_splat[i].get())), + rgb_splat: core::array::from_fn(|i| AtomicFloat::new(self.rgb_splat[i].get())), bucket_offset: self.bucket_offset, } } @@ -309,9 +310,9 @@ impl Clone for SpectralPixel { impl Default for SpectralPixel { fn default() -> Self { Self { - rgb_sum: std::array::from_fn(|_| AtomicFloat::new(0.0)), + rgb_sum: core::array::from_fn(|_| AtomicFloat::new(0.0)), rgb_weight_sum: AtomicFloat::new(0.0), - rgb_splat: std::array::from_fn(|_| AtomicFloat::new(0.0)), + rgb_splat: core::array::from_fn(|_| AtomicFloat::new(0.0)), bucket_offset: 0, } } diff --git a/shared/src/core/geometry/bounds.rs b/shared/src/core/geometry/bounds.rs index 2489de9..6c59df2 100644 --- a/shared/src/core/geometry/bounds.rs +++ b/shared/src/core/geometry/bounds.rs @@ -1,12 +1,12 @@ use super::{Float, NumFloat}; use super::{Point, Point2f, Point3, Point3f, Vector, Vector2, Vector2f, Vector3, Vector3f}; -use crate::core::geometry::traits::{Sqrt, VectorLike}; +use crate::core::geometry::traits::{SqrtExt, VectorLike}; use crate::core::geometry::{max, min}; use crate::utils::interval::Interval; use crate::utils::math::lerp; +use core::mem; +use core::ops::{Add, Div, DivAssign, Mul, Sub}; use num_traits::{Bounded, Num}; -use std::mem; -use std::ops::{Add, Div, DivAssign, Mul, Sub}; // AABB BOUNDING BOXES @@ -18,7 +18,7 @@ pub struct Bounds { impl<'a, T, const N: usize> IntoIterator for &'a Bounds { type Item = &'a Point; - type IntoIter = std::array::IntoIter<&'a Point, 2>; + type IntoIter = core::array::IntoIter<&'a Point, 2>; fn into_iter(self) -> Self::IntoIter { [&self.p_min, &self.p_max].into_iter() @@ -137,7 +137,7 @@ where } pub fn corner(&self, corner_index: usize) -> Point { - Point(std::array::from_fn(|i| { + Point(core::array::from_fn(|i| { if (corner_index >> i) & 1 == 1 { self.p_max[i] } else { @@ -206,7 +206,7 @@ where impl Bounds3 where - T: NumFloat + PartialOrd + Copy + Default + Sqrt, + T: NumFloat + PartialOrd + Copy + Default + SqrtExt, { pub fn bounding_sphere(&self) -> (Point3, T) { let two = T::one() + T::one(); diff --git a/shared/src/core/geometry/cone.rs b/shared/src/core/geometry/cone.rs index c09b5d1..edaa7b2 100644 --- a/shared/src/core/geometry/cone.rs +++ b/shared/src/core/geometry/cone.rs @@ -1,6 +1,7 @@ use super::{Bounds3f, Float, PI, Point3f, Vector3f, VectorLike}; use crate::utils::math::{degrees, safe_acos, safe_asin, safe_sqrt, square}; use crate::utils::transform::TransformGeneric; +use num_traits::Float as NumFloat; #[derive(Debug, Clone)] pub struct DirectionCone { diff --git a/shared/src/core/geometry/mod.rs b/shared/src/core/geometry/mod.rs index 133c0d1..44cc4ff 100644 --- a/shared/src/core/geometry/mod.rs +++ b/shared/src/core/geometry/mod.rs @@ -12,7 +12,7 @@ pub use self::primitives::{ Vector3i, }; pub use self::ray::{Ray, RayDifferential}; -pub use self::traits::{Lerp, Sqrt, Tuple, VectorLike}; +pub use self::traits::{Lerp, SqrtExt, Tuple, VectorLike}; use crate::core::pbrt::{Float, PI}; use crate::utils::math::{clamp, square}; diff --git a/shared/src/core/geometry/primitives.rs b/shared/src/core/geometry/primitives.rs index 938ad1b..0b95fb5 100644 --- a/shared/src/core/geometry/primitives.rs +++ b/shared/src/core/geometry/primitives.rs @@ -1,13 +1,13 @@ -use super::traits::{Sqrt, Tuple, VectorLike}; +use super::traits::{SqrtExt, Tuple, VectorLike}; use super::{Float, NumFloat, PI}; use crate::utils::interval::Interval; use crate::utils::math::{clamp, difference_of_products, quadratic, safe_asin}; -use num_traits::{AsPrimitive, FloatConst, Num, Signed, Zero}; -use std::hash::{Hash, Hasher}; -use std::iter::Sum; -use std::ops::{ +use core::hash::{Hash, Hasher}; +use core::iter::Sum; +use core::ops::{ Add, AddAssign, Div, DivAssign, Index, IndexMut, Mul, MulAssign, Neg, Sub, SubAssign, }; +use num_traits::{AsPrimitive, FloatConst, Num, Signed, Zero}; pub trait MulAdd { type Output; @@ -18,7 +18,7 @@ impl MulAdd for Float { type Output = Float; #[inline(always)] fn mul_add(self, multiplier: Float, addend: Float) -> Self::Output { - self.mul_add(multiplier, addend) + num_traits::Float::mul_add(self, multiplier, addend) } } @@ -305,7 +305,7 @@ macro_rules! impl_float_vector_ops { + Mul + Sub + Div - + Sqrt, + + SqrtExt, { type Scalar = T; fn dot(self, rhs: Self) -> T { @@ -449,7 +449,7 @@ impl From> for Vector { impl Point where - T: NumFloat + Sqrt, + T: NumFloat + SqrtExt, { pub fn distance(self, other: Self) -> T { (self - other).norm() @@ -814,7 +814,7 @@ impl From> for Point { impl Normal3 where - T: Num + PartialOrd + Copy + Neg + Sqrt, + T: Num + PartialOrd + Copy + Neg + SqrtExt, { pub fn face_forward(self, v: impl Into>) -> Self { let v: Vector3 = v.into(); diff --git a/shared/src/core/geometry/traits.rs b/shared/src/core/geometry/traits.rs index 6491943..aad7bce 100644 --- a/shared/src/core/geometry/traits.rs +++ b/shared/src/core/geometry/traits.rs @@ -1,8 +1,8 @@ use crate::core::pbrt::Float; use crate::utils::interval::Interval; use crate::utils::math::{next_float_down, next_float_up}; +use core::ops::{Add, Div, DivAssign, Index, IndexMut, Mul, MulAssign, Neg, Sub}; use num_traits::{Float as NumFloat, FloatConst, Num, One, Signed, Zero}; -use std::ops::{Add, Div, DivAssign, Index, IndexMut, Mul, MulAssign, Neg, Sub}; pub trait Tuple: Sized + Copy + Index + IndexMut @@ -77,7 +77,7 @@ pub trait VectorLike: + Div + Mul { - type Scalar: Copy + Zero + Add + Mul + Sqrt; + type Scalar: Copy + Zero + Add + Mul + SqrtExt; fn dot(self, rhs: Self) -> Self::Scalar; fn norm_squared(self) -> Self::Scalar { @@ -96,7 +96,7 @@ pub trait VectorLike: } fn norm(&self) -> Self::Scalar { - self.norm_squared().sqrt() + self.norm_squared().sqrt_ext() } fn normalize(self) -> Self @@ -119,36 +119,36 @@ pub trait VectorLike: } } -pub trait Sqrt { - fn sqrt(self) -> Self; +pub trait SqrtExt { + fn sqrt_ext(self) -> Self; } -impl Sqrt for Float { - fn sqrt(self) -> Self { - self.sqrt() +impl SqrtExt for Float { + fn sqrt_ext(self) -> Self { + ::sqrt(self) } } -impl Sqrt for f64 { - fn sqrt(self) -> Self { - self.sqrt() +impl SqrtExt for f64 { + fn sqrt_ext(self) -> Self { + ::sqrt(self) } } -impl Sqrt for i32 { - fn sqrt(self) -> Self { +impl SqrtExt for i32 { + fn sqrt_ext(self) -> Self { self.isqrt() } } -impl Sqrt for u32 { - fn sqrt(self) -> Self { +impl SqrtExt for u32 { + fn sqrt_ext(self) -> Self { self.isqrt() } } -impl Sqrt for Interval { - fn sqrt(self) -> Self { +impl SqrtExt for Interval { + fn sqrt_ext(self) -> Self { let low = if self.low < 0.0 { 0.0 } else { diff --git a/shared/src/core/image.rs b/shared/src/core/image.rs index 421a127..fe6d702 100644 --- a/shared/src/core/image.rs +++ b/shared/src/core/image.rs @@ -3,11 +3,10 @@ use crate::core::color::{ColorEncoding, ColorEncodingTrait, LINEAR}; use crate::core::geometry::{Bounds2f, Point2f, Point2fi, Point2i}; use crate::utils::Ptr; use crate::utils::containers::DeviceArray2D; -use crate::utils::math::{f16_to_f32, lerp, square}; +use crate::utils::math::{f16_to_f32_software, lerp, square}; use core::hash; -use half::f16; -use smallvec::{SmallVec, smallvec}; -use std::ops::{Deref, DerefMut}; +use core::ops::{Deref, DerefMut}; +use num_traits::Float as NumFloat; #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum WrapMode { @@ -36,8 +35,8 @@ pub enum PixelFormat { F32, } -impl std::fmt::Display for PixelFormat { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +impl core::fmt::Display for PixelFormat { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { match self { PixelFormat::U8 => write!(f, "U256"), PixelFormat::F16 => write!(f, "Half"), @@ -72,7 +71,7 @@ impl PixelFormat { #[derive(Clone, Copy, Debug)] pub enum Pixels { U8(Ptr), - F16(Ptr), + F16(Ptr), F32(Ptr), } @@ -180,7 +179,7 @@ impl ImageAccess for DeviceImage { } Pixels::F16(ptr) => { let raw_val = *ptr.add(offset as usize); - raw_val.to_f32() + f16_to_f32_software(raw_val) } Pixels::F32(ptr) => *ptr.add(offset as usize), } diff --git a/shared/src/core/interaction.rs b/shared/src/core/interaction.rs index 7ac3288..7ee2fd2 100644 --- a/shared/src/core/interaction.rs +++ b/shared/src/core/interaction.rs @@ -13,7 +13,6 @@ use crate::core::material::{ Material, MaterialEvalContext, MaterialTrait, NormalBumpEvalContext, bump_map, normal_map, }; use crate::core::medium::{Medium, MediumInterface, PhaseFunction}; -use crate::core::options::get_options; use crate::core::sampler::{Sampler, SamplerTrait}; use crate::core::shape::Shape; use crate::core::texture::{GPUFloatTexture, UniversalTextureEvaluator}; @@ -21,8 +20,6 @@ use crate::spectra::{SampledSpectrum, SampledWavelengths}; use crate::utils::Ptr; use crate::utils::math::{clamp, difference_of_products, square}; use enum_dispatch::enum_dispatch; -use std::any::Any; -use std::default; #[repr(C)] #[derive(Default, Copy, Clone, Debug)] @@ -347,69 +344,6 @@ impl SurfaceInteraction { } } - #[cfg(not(target_os = "cuda"))] - pub fn get_bsdf( - &mut self, - r: &Ray, - lambda: &SampledWavelengths, - camera: &Camera, - sampler: &mut Sampler, - ) -> Option { - self.compute_differentials(r, camera, sampler.samples_per_pixel() as i32); - - let material = { - let root_mat = self.material; - let mut active_mat: &Material = &*root_mat; - let tex_eval = UniversalTextureEvaluator; - while let Material::Mix(mix) = active_mat { - // We need a context to evaluate the 'amount' texture - let ctx = MaterialEvalContext::from(&*self); - active_mat = mix.choose_material(&tex_eval, &ctx)?; - } - active_mat.clone() - }; - - let ctx = MaterialEvalContext::from(&*self); - let tex_eval = UniversalTextureEvaluator; - let displacement = material.get_displacement(); - let normal_map = Ptr::from(material.get_normal_map().unwrap()); - if !displacement.is_null() || !normal_map.is_null() { - // This calls the function defined above - self.compute_bump_geom(&tex_eval, displacement, normal_map); - } - - let mut bsdf = material.get_bsdf(&tex_eval, &ctx, lambda); - if get_options().force_diffuse { - let r = bsdf.rho_wo(self.common.wo, &[sampler.get1d()], &[sampler.get2d()]); - let diff_bxdf = BxDF::Diffuse(DiffuseBxDF::new(r)); - bsdf = BSDF::new(self.shading.n, self.shading.dpdu, Ptr::from(&diff_bxdf)); - } - Some(bsdf) - } - - #[cfg(not(target_os = "cuda"))] - pub fn get_bssrdf( - &self, - _ray: &Ray, - lambda: &SampledWavelengths, - _camera: &Camera, - ) -> Option { - let material = { - let mut active_mat = unsafe { self.material.as_ref() }; - let tex_eval = UniversalTextureEvaluator; - while let Material::Mix(mix) = active_mat { - // We need a context to evaluate the 'amount' texture - let ctx = MaterialEvalContext::from(self); - active_mat = mix.choose_material(&tex_eval, &ctx)?; - } - active_mat.clone() - }; - - let ctx = MaterialEvalContext::from(self); - let tex_eval = UniversalTextureEvaluator; - material.get_bssrdf(&tex_eval, &ctx, lambda) - } - fn compute_bump_geom( &mut self, tex_eval: &UniversalTextureEvaluator, diff --git a/shared/src/core/material.rs b/shared/src/core/material.rs index 3d47319..233dd0f 100644 --- a/shared/src/core/material.rs +++ b/shared/src/core/material.rs @@ -1,6 +1,6 @@ use crate::materials::*; +use core::ops::Deref; use enum_dispatch::enum_dispatch; -use std::ops::Deref; use crate::Float; use crate::bxdfs::{ diff --git a/shared/src/core/medium.rs b/shared/src/core/medium.rs index ae9a844..a957c7c 100644 --- a/shared/src/core/medium.rs +++ b/shared/src/core/medium.rs @@ -1,5 +1,4 @@ use enum_dispatch::enum_dispatch; -use std::sync::Arc; use crate::core::geometry::{ Bounds3f, Frame, Point2f, Point3f, Point3i, Ray, Vector3f, VectorLike, spherical_direction, @@ -15,6 +14,7 @@ use crate::utils::math::{clamp, square}; use crate::utils::ptr::Ptr; use crate::utils::rng::Rng; use crate::utils::transform::Transform; +use num_traits::Float as NumFloat; #[repr(C)] #[derive(Debug, Clone, Copy)] @@ -371,7 +371,7 @@ impl MediumProperties { } #[enum_dispatch] -pub trait MediumTrait: Send + Sync + std::fmt::Debug { +pub trait MediumTrait: Send + Sync + core::fmt::Debug { fn is_emissive(&self) -> bool; fn sample_point(&self, p: Point3f, lambda: &SampledWavelengths) -> MediumProperties; fn sample_ray( diff --git a/shared/src/core/mod.rs b/shared/src/core/mod.rs index 092a047..f5db8f2 100644 --- a/shared/src/core/mod.rs +++ b/shared/src/core/mod.rs @@ -11,7 +11,6 @@ pub mod interaction; pub mod light; pub mod material; pub mod medium; -pub mod options; pub mod pbrt; pub mod primitive; pub mod sampler; diff --git a/shared/src/core/pbrt.rs b/shared/src/core/pbrt.rs index 99ceeff..b276c5a 100644 --- a/shared/src/core/pbrt.rs +++ b/shared/src/core/pbrt.rs @@ -1,9 +1,6 @@ use crate::core::geometry::Lerp; -use core::sync::atomic::{AtomicU64, Ordering as SyncOrdering}; +use core::ops::{Add, Mul}; use num_traits::{Num, PrimInt}; -use std::hash::Hash; -use std::ops::{Add, Mul}; -use std::sync::{Arc, Mutex}; use crate::core::image::DeviceImage; use crate::core::light::LightTrait; @@ -15,40 +12,6 @@ use crate::utils::Ptr; pub type Float = f32; -// #[derive(Copy, Clone, Debug)] -// pub struct Host; -// -// #[derive(Copy, Clone, Debug)] -// pub struct Device; -// -// pub trait Backend: Copy + Clone + 'static { -// type ShapeRef: Copy + Clone; -// type TextureRef: Copy + Clone; -// type ImageRef: Copy + Clone; -// type DenseSpectrumRef = Ptr; -// type ColorSpaceRef = Ptr; -// type DiffuseLight: LightTrait; -// type PointLight: LightTrait; -// type UniformInfiniteLight: LightTrait; -// type PortalInfiniteLight: LightTrait; -// type ImageInfiniteLight: LightTrait; -// type SpotLight: LightTrait; -// } -// -// impl Backend for Device { -// type ShapeRef = Ptr; -// type TextureRef = Ptr; -// type ColorSpaceRef = Ptr; -// type DenseSpectrumRef = Ptr; -// type ImageRef = Ptr; -// type DiffuseLight = Ptr>; -// type PointLight = Ptr>; -// type UniformInfiniteLight = Ptr>; -// type PortalInfiniteLight = Ptr>; -// type ImageInfiniteLight = Ptr>; -// type SpotLight = Ptr>; -// } -// #[cfg(not(feature = "use_f64"))] pub type FloatBits = u32; @@ -134,45 +97,50 @@ impl FloatBitOps for f64 { pub const MACHINE_EPSILON: Float = Float::EPSILON * 0.5; pub const SHADOW_EPSILON: Float = 0.0001; pub const ONE_MINUS_EPSILON: Float = 0.99999994; -pub const PI: Float = std::f32::consts::PI; -pub const INV_PI: Float = 0.318_309_886_183_790_671_54; +pub const PI: Float = core::f32::consts::PI; +pub const INV_PI: Float = core::f32::consts::FRAC_1_PI; pub const INV_2_PI: Float = 0.159_154_943_091_895_335_77; pub const INV_4_PI: Float = 0.079_577_471_545_947_667_88; -pub const PI_OVER_2: Float = 1.570_796_326_794_896_619_23; -pub const PI_OVER_4: Float = 0.785_398_163_397_448_309_61; -pub const SQRT_2: Float = 1.414_213_562_373_095_048_80; +pub const PI_OVER_2: Float = core::f32::consts::FRAC_PI_2; +pub const PI_OVER_4: Float = core::f32::consts::FRAC_PI_4; +pub const SQRT_2: Float = core::f32::consts::SQRT_2; #[inline] pub fn gamma(n: i32) -> Float { n as Float * MACHINE_EPSILON / (1. - n as Float * MACHINE_EPSILON) } -pub static RARE_EVENT_TOTAL_CALLS: AtomicU64 = AtomicU64::new(0); -pub static RARE_EVENT_CONDITION_MET: AtomicU64 = AtomicU64::new(0); +#[cfg(feature = "cpu_debug")] +pub mod debug { + use core::sync::atomic::AtomicU64; + use core::sync::atomic::Ordering as SyncOrdering; + pub static RARE_EVENT_TOTAL_CALLS: AtomicU64 = AtomicU64::new(0); + pub static RARE_EVENT_CONDITION_MET: AtomicU64 = AtomicU64::new(0); -#[macro_export] -macro_rules! check_rare { - ($frequency_threshold:expr, $condition:expr) => { - use core::sync::atomic::{AtomicU64, Ordering as SyncOrdering}; - const CHECK_INTERVAL: u64 = 4096; + #[macro_export] + macro_rules! check_rare { + ($frequency_threshold:expr, $condition:expr) => { + use core::sync::atomic::{AtomicU64, Ordering as SyncOrdering}; + const CHECK_INTERVAL: u64 = 4096; - let total_calls = RARE_EVENT_TOTAL_CALLS.fetch_add(1, SyncOrdering::Relaxed); + let total_calls = RARE_EVENT_TOTAL_CALLS.fetch_add(1, SyncOrdering::Relaxed); - if $condition { - RARE_EVENT_CONDITION_MET.fetch_add(1, SyncOrdering::Relaxed); - } + if $condition { + RARE_EVENT_CONDITION_MET.fetch_add(1, SyncOrdering::Relaxed); + } - if (total_calls + 1) % CHECK_INTERVAL == 0 { - let met_count = RARE_EVENT_CONDITION_MET.load(SyncOrdering::Relaxed); - if met_count > 0 { - let frequency = met_count as f64 / (total_calls + 1) as f64; - if frequency > $frequency_threshold { - panic!( - "Rare event occurred with frequency {} which is > threshold {}", - frequency, $frequency_threshold - ); + if (total_calls + 1) % CHECK_INTERVAL == 0 { + let met_count = RARE_EVENT_CONDITION_MET.load(SyncOrdering::Relaxed); + if met_count > 0 { + let frequency = met_count as f64 / (total_calls + 1) as f64; + if frequency > $frequency_threshold { + panic!( + "Rare event occurred with frequency {} which is > threshold {}", + frequency, $frequency_threshold + ); + } } } - } - }; + }; + } } diff --git a/shared/src/core/primitive.rs b/shared/src/core/primitive.rs index a7080d5..2325848 100644 --- a/shared/src/core/primitive.rs +++ b/shared/src/core/primitive.rs @@ -11,7 +11,6 @@ use crate::utils::hash::hash_float; use crate::utils::transform::{AnimatedTransform, Transform}; use enum_dispatch::enum_dispatch; -use std::sync::Arc; #[enum_dispatch] pub trait PrimitiveTrait: Send + Sync { diff --git a/shared/src/core/sampler.rs b/shared/src/core/sampler.rs index 5f9faef..6022a3a 100644 --- a/shared/src/core/sampler.rs +++ b/shared/src/core/sampler.rs @@ -1,6 +1,5 @@ use crate::core::filter::FilterTrait; use crate::core::geometry::{Bounds2f, Point2f, Point2i, Vector2f}; -use crate::core::options::{PBRTOptions, get_options}; use crate::core::pbrt::{Float, ONE_MINUS_EPSILON, PI, PI_OVER_2, PI_OVER_4}; use crate::utils::Ptr; use crate::utils::containers::DeviceArray2D; @@ -14,7 +13,6 @@ use crate::utils::rng::Rng; use crate::utils::sobol::N_SOBOL_DIMENSIONS; use crate::utils::{hash::*, sobol}; use enum_dispatch::enum_dispatch; -use rand::seq::index::sample; #[repr(C)] #[derive(Debug, Default, Clone, Copy)] diff --git a/shared/src/core/scattering.rs b/shared/src/core/scattering.rs index 1bd49d1..37f1ae4 100644 --- a/shared/src/core/scattering.rs +++ b/shared/src/core/scattering.rs @@ -6,8 +6,9 @@ use crate::core::pbrt::{Float, PI}; use crate::spectra::{N_SPECTRUM_SAMPLES, SampledSpectrum}; use crate::utils::math::{clamp, lerp, safe_sqrt, square}; use crate::utils::sampling::sample_uniform_disk_polar; +use num_traits::Float as NumFloat; -use num::complex::Complex; +use crate::utils::complex::Complex; #[repr(C)] #[derive(Debug, Default, Clone, Copy)] @@ -148,11 +149,11 @@ pub fn fr_dielectric(cos_theta_i: Float, eta: Float) -> Float { (square(r_parl) + square(r_perp)) / 2. } -pub fn fr_complex(cos_theta_i: Float, eta: Complex) -> Float { +pub fn fr_complex(cos_theta_i: Float, eta: Complex) -> Float { let cos_corr = clamp(cos_theta_i, 0., 1.); let sin2_theta_i = 1. - square(cos_corr); - let sin2_theta_t: Complex = sin2_theta_i / square(eta); - let cos2_theta_t: Complex = (1. - sin2_theta_t).sqrt(); + let sin2_theta_t: Complex = sin2_theta_i / square(eta); + let cos2_theta_t: Complex = (1. - sin2_theta_t).sqrt(); let r_parl = (eta * cos_corr - cos2_theta_t) / (eta * cos_corr + cos2_theta_t); let r_perp = (cos_corr - eta * cos2_theta_t) / (cos_corr + eta * cos2_theta_t); diff --git a/shared/src/core/texture.rs b/shared/src/core/texture.rs index 048b761..fc3d3dd 100644 --- a/shared/src/core/texture.rs +++ b/shared/src/core/texture.rs @@ -14,6 +14,7 @@ use crate::utils::Transform; use crate::utils::math::square; use crate::{Float, INV_2_PI, INV_PI, PI}; use enum_dispatch::enum_dispatch; +use num_traits::Float as NumFloat; pub use crate::textures::*; diff --git a/shared/src/data.rs b/shared/src/data.rs index 4bd4b90..f5a185e 100644 --- a/shared/src/data.rs +++ b/shared/src/data.rs @@ -1,6 +1,5 @@ use crate::Float; use bytemuck::cast_slice; -use once_cell::sync::Lazy; #[repr(C, align(16))] struct AlignedData(pub [u8; N]); diff --git a/shared/src/filters/lanczos.rs b/shared/src/filters/lanczos.rs index 710b252..700c110 100644 --- a/shared/src/filters/lanczos.rs +++ b/shared/src/filters/lanczos.rs @@ -2,7 +2,6 @@ use crate::Float; use crate::core::filter::{FilterSample, FilterSampler, FilterTrait}; use crate::core::geometry::{Point2f, Vector2f}; use crate::utils::math::{lerp, windowed_sinc}; -use rand::Rng; #[repr(C)] #[derive(Clone, Debug, Copy)] @@ -10,6 +9,7 @@ pub struct LanczosSincFilter { pub radius: Vector2f, pub tau: Float, pub sampler: FilterSampler, + pub integral: Float, } impl FilterTrait for LanczosSincFilter { @@ -23,26 +23,7 @@ impl FilterTrait for LanczosSincFilter { } fn integral(&self) -> Float { - let sqrt_samples = 64; - let n_samples = sqrt_samples * sqrt_samples; - let area = (2.0 * self.radius.x()) * (2.0 * self.radius.y()); - let mut sum = 0.0; - let mut rng = rand::rng(); - - for y in 0..sqrt_samples { - for x in 0..sqrt_samples { - let u = Point2f::new( - (x as Float + rng.random::()) / sqrt_samples as Float, - (y as Float + rng.random::()) / sqrt_samples as Float, - ); - let p = Point2f::new( - lerp(u.x(), -self.radius.x(), self.radius.x()), - lerp(u.y(), -self.radius.y(), self.radius.y()), - ); - sum += self.evaluate(p); - } - } - sum / n_samples as Float * area + self.integral } fn sample(&self, u: Point2f) -> FilterSample { diff --git a/shared/src/filters/mitchell.rs b/shared/src/filters/mitchell.rs index 4ee08db..03425ad 100644 --- a/shared/src/filters/mitchell.rs +++ b/shared/src/filters/mitchell.rs @@ -1,6 +1,7 @@ use crate::Float; use crate::core::filter::{FilterSample, FilterSampler, FilterTrait}; use crate::core::geometry::{Point2f, Vector2f}; +use num_traits::Float as NumFloat; #[repr(C)] #[derive(Clone, Copy, Debug)] diff --git a/shared/src/filters/triangle.rs b/shared/src/filters/triangle.rs index 774f08a..1572ca5 100644 --- a/shared/src/filters/triangle.rs +++ b/shared/src/filters/triangle.rs @@ -2,6 +2,7 @@ use crate::Float; use crate::core::filter::{FilterSample, FilterTrait}; use crate::core::geometry::{Point2f, Vector2f}; use crate::utils::math::sample_tent; +use num_traits::Float as NumFloat; #[repr(C)] #[derive(Clone, Debug, Copy)] diff --git a/shared/src/lib.rs b/shared/src/lib.rs index a2d294c..29a12dc 100644 --- a/shared/src/lib.rs +++ b/shared/src/lib.rs @@ -1,7 +1,6 @@ #![allow(unused_imports, dead_code)] -#![feature(float_erf)] -#![feature(f16)] #![feature(associated_type_defaults)] +#![no_std] pub mod bxdfs; pub mod cameras; @@ -16,4 +15,5 @@ pub mod textures; pub mod utils; pub use core::pbrt::*; +pub use utils::PBRTOptions; pub use utils::ptr::Ptr; diff --git a/shared/src/lights/diffuse.rs b/shared/src/lights/diffuse.rs index 0341432..6d09aa3 100644 --- a/shared/src/lights/diffuse.rs +++ b/shared/src/lights/diffuse.rs @@ -17,6 +17,7 @@ use crate::spectra::*; use crate::utils::hash::hash_float; use crate::utils::{Ptr, Transform}; use crate::{Float, PI}; +use num_traits::Float as NumFloat; #[repr(C)] #[derive(Clone, Debug, Copy)] diff --git a/shared/src/lights/distant.rs b/shared/src/lights/distant.rs index 070097a..017cca0 100644 --- a/shared/src/lights/distant.rs +++ b/shared/src/lights/distant.rs @@ -7,6 +7,7 @@ use crate::core::spectrum::SpectrumTrait; use crate::spectra::{DenselySampledSpectrum, SampledSpectrum, SampledWavelengths}; use crate::utils::Ptr; use crate::{Float, PI}; +use num_traits::Float as NumFloat; #[repr(C)] #[derive(Clone, Copy, Debug)] diff --git a/shared/src/lights/infinite.rs b/shared/src/lights/infinite.rs index c66faf3..779a159 100644 --- a/shared/src/lights/infinite.rs +++ b/shared/src/lights/infinite.rs @@ -20,7 +20,7 @@ use crate::utils::sampling::{ }; use crate::utils::{Ptr, Transform}; use crate::{Float, PI}; -use std::sync::Arc; +use num_traits::Float as NumFloat; #[repr(C)] #[derive(Debug, Copy, Clone)] diff --git a/shared/src/lights/point.rs b/shared/src/lights/point.rs index c2daa2a..6aab4c2 100644 --- a/shared/src/lights/point.rs +++ b/shared/src/lights/point.rs @@ -9,6 +9,7 @@ use crate::core::spectrum::SpectrumTrait; use crate::spectra::{DenselySampledSpectrum, SampledSpectrum, SampledWavelengths}; use crate::utils::Ptr; use crate::{Float, PI}; +use num_traits::Float as NumFloat; #[repr(C)] #[derive(Clone, Copy, Debug)] diff --git a/shared/src/lights/projection.rs b/shared/src/lights/projection.rs index ef08a9a..67b65e4 100644 --- a/shared/src/lights/projection.rs +++ b/shared/src/lights/projection.rs @@ -15,6 +15,7 @@ use crate::{ spectra::{RGBColorSpace, RGBIlluminantSpectrum}, utils::{Ptr, Transform, sampling::DevicePiecewiseConstant2D}, }; +use num_traits::Float as NumFloat; #[repr(C)] #[derive(Clone, Copy, Debug)] diff --git a/shared/src/lights/sampler.rs b/shared/src/lights/sampler.rs index a25ce8b..47f3d1f 100644 --- a/shared/src/lights/sampler.rs +++ b/shared/src/lights/sampler.rs @@ -10,6 +10,7 @@ use crate::utils::ptr::Ptr; use crate::utils::sampling::AliasTable; use crate::{Float, ONE_MINUS_EPSILON, PI}; use enum_dispatch::enum_dispatch; +use num_traits::Float as NumFloat; #[derive(Clone, Copy, Debug, Default)] #[repr(C)] @@ -26,7 +27,7 @@ pub struct CompactLightBounds { pub qb: [[u16; 3]; 2], } -const _: () = assert!(std::mem::size_of::() == 24); +const _: () = assert!(core::mem::size_of::() == 24); impl CompactLightBounds { pub fn new(lb: &LightBounds, all_b: &Bounds3f) -> Self { @@ -291,7 +292,7 @@ pub struct LightBVHNode { packed_data: u32, } -const _: () = assert!(std::mem::size_of::() == 32); +const _: () = assert!(core::mem::size_of::() == 32); impl LightBVHNode { /// Mask to isolate the Leaf Flag (Bit 31) diff --git a/shared/src/lights/spot.rs b/shared/src/lights/spot.rs index f5ee84f..3a86855 100644 --- a/shared/src/lights/spot.rs +++ b/shared/src/lights/spot.rs @@ -7,6 +7,7 @@ use crate::core::spectrum::SpectrumTrait; use crate::spectra::{DenselySampledSpectrum, SampledSpectrum, SampledWavelengths}; use crate::utils::Ptr; use crate::{Float, PI}; +use num_traits::Float as NumFloat; #[repr(C)] #[derive(Clone, Copy, Debug)] diff --git a/shared/src/materials/coated.rs b/shared/src/materials/coated.rs index 2b030db..6152acb 100644 --- a/shared/src/materials/coated.rs +++ b/shared/src/materials/coated.rs @@ -28,6 +28,7 @@ pub struct CoatedDiffuseMaterial { pub max_depth: u32, pub n_samples: u32, pub remap_roughness: bool, + pub seed: i32, } impl CoatedDiffuseMaterial { @@ -45,6 +46,7 @@ impl CoatedDiffuseMaterial { remap_roughness: bool, max_depth: u32, n_samples: u32, + seed: i32, ) -> Self { Self { displacement, @@ -59,6 +61,7 @@ impl CoatedDiffuseMaterial { remap_roughness, max_depth, n_samples, + seed, } } } @@ -112,6 +115,7 @@ impl MaterialTrait for CoatedDiffuseMaterial { gg, self.max_depth, self.n_samples, + self.seed, )); BSDF::new(ctx.ns, ctx.dpdus, Ptr::from(&bxdf)) @@ -165,6 +169,7 @@ pub struct CoatedConductorMaterial { max_depth: u32, n_samples: u32, remap_roughness: bool, + seed: i32, } impl CoatedConductorMaterial { @@ -186,6 +191,7 @@ impl CoatedConductorMaterial { max_depth: u32, n_samples: u32, remap_roughness: bool, + seed: i32, ) -> Self { Self { displacement, @@ -204,6 +210,7 @@ impl CoatedConductorMaterial { remap_roughness, max_depth, n_samples, + seed, } } } @@ -279,6 +286,7 @@ impl MaterialTrait for CoatedConductorMaterial { gg, self.max_depth, self.n_samples, + self.seed, )); BSDF::new(ctx.ns, ctx.dpdus, Ptr::from(&bxdf)) } @@ -302,20 +310,24 @@ impl MaterialTrait for CoatedConductorMaterial { self.conductor_vroughness, ]; - let mut spectrum_textures = Vec::with_capacity(4); + let mut spectrum_textures = [Ptr::null(); 4]; + let mut n = 0; - spectrum_textures.push(self.albedo); + spectrum_textures[n] = self.albedo; + n += 1; if !self.conductor_eta.is_null() { - spectrum_textures.push(self.conductor_eta); + spectrum_textures[n] = self.conductor_eta; + n += 1; } if !self.k.is_null() { - spectrum_textures.push(self.k); + spectrum_textures[n] = self.k; + n += 1; } if !self.conductor_eta.is_null() { - spectrum_textures.push(self.reflectance); + spectrum_textures[n] = self.reflectance; } tex_eval.can_evaluate(&float_textures, &spectrum_textures) diff --git a/shared/src/shapes/curves.rs b/shared/src/shapes/curves.rs index d16c933..df45dfe 100644 --- a/shared/src/shapes/curves.rs +++ b/shared/src/shapes/curves.rs @@ -10,6 +10,7 @@ use crate::utils::splines::{ bound_cubic_bezier, cubic_bezier_control_points, evaluate_cubic_bezier, subdivide_cubic_bezier, }; use crate::utils::transform::{Transform, look_at}; +use num_traits::Float as NumFloat; #[repr(C)] #[derive(Debug, Clone, Copy, PartialEq)] diff --git a/shared/src/shapes/cylinder.rs b/shared/src/shapes/cylinder.rs index 956f25b..e15f8df 100644 --- a/shared/src/shapes/cylinder.rs +++ b/shared/src/shapes/cylinder.rs @@ -12,10 +12,11 @@ use crate::utils::splines::{ use crate::utils::transform::{Transform, look_at}; use crate::{Float, PI, gamma}; -use crate::core::geometry::{Sqrt, Tuple}; +use crate::core::geometry::{SqrtExt, Tuple}; use crate::utils::interval::Interval; use crate::utils::math::{clamp, difference_of_products, lerp, square}; -use std::mem; +use core::mem; +use num_traits::Float as NumFloat; #[repr(C)] #[derive(Debug, Clone, Copy)] @@ -68,13 +69,13 @@ impl CylinderShape { let f = b / (2. * a); let vx: Interval = oi.x() - f * di.x(); let vy: Interval = oi.y() - f * di.y(); - let length: Interval = (square(vx) + square(vy)).sqrt(); + let length: Interval = (square(vx) + square(vy)).sqrt_ext(); let discrim: Interval = 4. * a * (Interval::new(self.radius) * length) * (Interval::new(self.radius) - length); if discrim.low < 0. { return None; } - let root_discrim = discrim.sqrt(); + let root_discrim = discrim.sqrt_ext(); let q = if Float::from(b) < 0. { -0.5 * (b - root_discrim) } else { diff --git a/shared/src/shapes/disk.rs b/shared/src/shapes/disk.rs index 2b25e89..159b69f 100644 --- a/shared/src/shapes/disk.rs +++ b/shared/src/shapes/disk.rs @@ -10,7 +10,7 @@ use crate::utils::Transform; use crate::utils::math::square; use crate::utils::sampling::sample_uniform_disk_concentric; use crate::{Float, PI}; -use std::sync::Arc; +use num_traits::Float as NumFloat; #[repr(C)] #[derive(Debug, Clone, Copy)] diff --git a/shared/src/shapes/sphere.rs b/shared/src/shapes/sphere.rs index 5bffb78..0d11077 100644 --- a/shared/src/shapes/sphere.rs +++ b/shared/src/shapes/sphere.rs @@ -2,7 +2,7 @@ use crate::core::geometry::{ Bounds3f, DirectionCone, Normal3f, Point2f, Point3f, Point3fi, Ray, Vector2f, Vector3f, Vector3fi, VectorLike, }; -use crate::core::geometry::{Frame, Sqrt, spherical_direction}; +use crate::core::geometry::{Frame, SqrtExt, spherical_direction}; use crate::core::interaction::{Interaction, InteractionTrait, SurfaceInteraction}; use crate::core::pbrt::gamma; use crate::core::shape::{ @@ -13,9 +13,9 @@ use crate::utils::interval::Interval; use crate::utils::math::{clamp, difference_of_products, radians, safe_acos, safe_sqrt, square}; use crate::utils::sampling::sample_uniform_sphere; use crate::{Float, PI}; +use num_traits::Float as NumFloat; -use std::mem; -use std::sync::Arc; +use core::mem; #[repr(C)] #[derive(Debug, Clone, Copy)] @@ -94,7 +94,7 @@ impl SphereShape { return None; } - let root_discrim = discrim.sqrt(); + let root_discrim = discrim.sqrt_ext(); let q = if Float::from(b) < 0. { -0.5 * (b - root_discrim) diff --git a/shared/src/shapes/triangle.rs b/shared/src/shapes/triangle.rs index 951367a..8a4d543 100644 --- a/shared/src/shapes/triangle.rs +++ b/shared/src/shapes/triangle.rs @@ -3,7 +3,7 @@ use crate::core::geometry::{ Bounds3f, DirectionCone, Normal, Normal3f, Point2f, Point3f, Point3fi, Ray, Vector2f, Vector3, Vector3f, }; -use crate::core::geometry::{Sqrt, Tuple, VectorLike, spherical_triangle_area}; +use crate::core::geometry::{SqrtExt, Tuple, VectorLike, spherical_triangle_area}; use crate::core::interaction::{ Interaction, InteractionBase, InteractionTrait, SimpleInteraction, SurfaceInteraction, }; diff --git a/shared/src/spectra/colorspace.rs b/shared/src/spectra/colorspace.rs index 7b28c01..e886601 100644 --- a/shared/src/spectra/colorspace.rs +++ b/shared/src/spectra/colorspace.rs @@ -4,9 +4,7 @@ use crate::core::pbrt::Float; use crate::spectra::{DenselySampledSpectrum, SampledSpectrum}; use crate::utils::math::SquareMatrix3f; use crate::utils::ptr::Ptr; -use anyhow::{Result, anyhow}; - -use std::cmp::{Eq, PartialEq}; +use core::cmp::{Eq, PartialEq}; #[repr(C)] #[derive(Copy, Debug, Clone)] @@ -19,13 +17,14 @@ pub struct DeviceStandardColorSpaces { impl DeviceStandardColorSpaces { #[cfg(not(target_arch = "nvptx64"))] - pub fn get_named(&self, name: &str) -> Result> { - match name.to_lowercase().as_str() { - "srgb" => Ok(self.srgb), - "dci-p3" => Ok(self.dci_p3), - "rec2020" => Ok(self.rec2020), - "aces2065-1" => Ok(self.aces2065_1), - _ => Err(anyhow!("No such spectrum")), + pub fn get_named(&self, name: &str) -> Option> { + let lower = name.as_bytes(); + match lower { + b if b.eq_ignore_ascii_case(b"srgb") => Some(self.srgb), + b if b.eq_ignore_ascii_case(b"dci-p3") => Some(self.dci_p3), + b if b.eq_ignore_ascii_case(b"rec2020") => Some(self.rec2020), + b if b.eq_ignore_ascii_case(b"aces2065-1") => Some(self.aces2065_1), + _ => None, } } @@ -51,11 +50,12 @@ pub enum ColorSpaceId { impl ColorSpaceId { #[cfg(not(target_arch = "nvptx64"))] pub fn from_name(name: &str) -> Option { - match name.to_lowercase().as_str() { - "srgb" => Some(Self::SRGB), - "dci-p3" => Some(Self::DciP3), - "rec2020" => Some(Self::Rec2020), - "aces2065-1" => Some(Self::Aces2065_1), + let lower = name.as_bytes(); + match lower { + b if b.eq_ignore_ascii_case(b"srgb") => Some(Self::SRGB), + b if b.eq_ignore_ascii_case(b"dci-p3") => Some(Self::DciP3), + b if b.eq_ignore_ascii_case(b"rec2020") => Some(Self::Rec2020), + b if b.eq_ignore_ascii_case(b"aces2065-1") => Some(Self::Aces2065_1), _ => None, } } diff --git a/shared/src/spectra/sampled.rs b/shared/src/spectra/sampled.rs index f57be43..39eb472 100644 --- a/shared/src/spectra/sampled.rs +++ b/shared/src/spectra/sampled.rs @@ -1,9 +1,10 @@ use crate::core::pbrt::Float; use crate::core::spectrum::{SpectrumTrait, StandardSpectra}; use crate::utils::math::{clamp, lerp}; -use std::ops::{ +use core::ops::{ Add, AddAssign, Div, DivAssign, Index, IndexMut, Mul, MulAssign, Neg, Sub, SubAssign, }; +use num_traits::Float as NumFloat; pub const CIE_Y_INTEGRAL: Float = 106.856895; @@ -42,7 +43,7 @@ impl SampledSpectrum { F: FnMut(usize) -> Float, { Self { - values: std::array::from_fn(cb), + values: core::array::from_fn(cb), } } @@ -130,7 +131,7 @@ impl SampledSpectrum { impl<'a> IntoIterator for &'a SampledSpectrum { type Item = &'a Float; - type IntoIter = std::slice::Iter<'a, Float>; + type IntoIter = core::slice::Iter<'a, Float>; fn into_iter(self) -> Self::IntoIter { self.values.iter() @@ -139,7 +140,7 @@ impl<'a> IntoIterator for &'a SampledSpectrum { impl<'a> IntoIterator for &'a mut SampledSpectrum { type Item = &'a mut Float; - type IntoIter = std::slice::IterMut<'a, Float>; + type IntoIter = core::slice::IterMut<'a, Float>; fn into_iter(self) -> Self::IntoIter { self.values.iter_mut() diff --git a/shared/src/spectra/simple.rs b/shared/src/spectra/simple.rs index 2b30fca..f533dc4 100644 --- a/shared/src/spectra/simple.rs +++ b/shared/src/spectra/simple.rs @@ -6,8 +6,7 @@ use crate::spectra::{N_SPECTRUM_SAMPLES, SampledSpectrum, SampledWavelengths}; use crate::utils::find_interval; use crate::utils::ptr::Ptr; use core::slice; -use std::hash::{Hash, Hasher}; -use std::sync::LazyLock; +use num_traits::Float as NumFloat; #[repr(C)] #[derive(Debug, Clone, Copy)] @@ -68,17 +67,6 @@ impl PartialEq for DenselySampledSpectrum { impl Eq for DenselySampledSpectrum {} -// impl Hash for DenselySampledSpectrum { -// fn hash(&self, state: &mut H) { -// self.lambda_min.hash(state); -// self.lambda_max.hash(state); -// -// for v in &self.values { -// v.to_bits().hash(state); -// } -// } -// } - impl SpectrumTrait for DenselySampledSpectrum { fn sample(&self, lambda: &SampledWavelengths) -> SampledSpectrum { let mut s = SampledSpectrum::default(); diff --git a/shared/src/textures/checkerboard.rs b/shared/src/textures/checkerboard.rs index 2b28fe2..c175e4b 100644 --- a/shared/src/textures/checkerboard.rs +++ b/shared/src/textures/checkerboard.rs @@ -5,6 +5,7 @@ use crate::core::texture::{ }; use crate::spectra::{SampledSpectrum, SampledWavelengths}; use crate::utils::{Ptr, math::square}; +use num_traits::Float as NumFloat; fn checkerboard( ctx: &TextureEvalContext, diff --git a/shared/src/textures/dots.rs b/shared/src/textures/dots.rs index 896466f..f2b2ccc 100644 --- a/shared/src/textures/dots.rs +++ b/shared/src/textures/dots.rs @@ -7,6 +7,7 @@ use crate::spectra::sampled::{SampledSpectrum, SampledWavelengths}; use crate::utils::Ptr; use crate::utils::math::square; use crate::utils::noise::noise_2d; +use num_traits::Float as NumFloat; fn inside_polka_dot(st: Point2f) -> bool { let s_cell = (st[0] + 0.5).floor(); diff --git a/shared/src/textures/marble.rs b/shared/src/textures/marble.rs index 47bd726..4f4229c 100644 --- a/shared/src/textures/marble.rs +++ b/shared/src/textures/marble.rs @@ -8,6 +8,7 @@ use crate::utils::math::clamp; use crate::utils::noise::fbm; use crate::utils::ptr::Ptr; use crate::utils::splines::evaluate_cubic_bezier; +use num_traits::Float as NumFloat; #[repr(C)] #[derive(Clone, Debug, Copy)] diff --git a/shared/src/utils/complex.rs b/shared/src/utils/complex.rs new file mode 100644 index 0000000..f4db74a --- /dev/null +++ b/shared/src/utils/complex.rs @@ -0,0 +1,115 @@ +use crate::Float; +use num_traits::Float as NumFloat; + +#[derive(Clone, Copy, Debug)] +pub struct Complex { + pub re: Float, + pub im: Float, +} + +impl Complex { + pub fn new(re: Float, im: Float) -> Self { + Self { re, im } + } + pub fn norm(&self) -> Float { + (self.re * self.re + self.im * self.im).sqrt() + } + pub fn sqrt(self) -> Self { + let r = self.norm(); + let re = ((r + self.re) / 2.0).sqrt(); + let im = self.im.signum() * ((r - self.re) / 2.0).sqrt(); + Self { re, im } + } +} + +impl core::ops::Add for Complex { + type Output = Self; + fn add(self, rhs: Self) -> Self { + Self { + re: self.re + rhs.re, + im: self.im + rhs.im, + } + } +} + +impl core::ops::Mul for Complex { + type Output = Self; + fn mul(self, rhs: Self) -> Self { + Self { + re: self.re * rhs.re - self.im * rhs.im, + im: self.re * rhs.im + self.im * rhs.re, + } + } +} + +impl core::ops::Div for Complex { + type Output = Self; + fn div(self, rhs: Self) -> Self { + let denom = rhs.re * rhs.re + rhs.im * rhs.im; + Self { + re: (self.re * rhs.re + self.im * rhs.im) / denom, + im: (self.im * rhs.re - self.re * rhs.im) / denom, + } + } +} + +impl core::ops::Sub for Complex { + type Output = Self; + fn sub(self, rhs: Self) -> Self { + Self { + re: self.re - rhs.re, + im: self.im - rhs.im, + } + } +} + +impl core::ops::Mul for Float { + type Output = Complex; + fn mul(self, rhs: Complex) -> Complex { + Complex { + re: self * rhs.re, + im: self * rhs.im, + } + } +} + +impl core::ops::Mul for Complex { + type Output = Complex; + fn mul(self, rhs: Float) -> Complex { + Complex { + re: self.re * rhs, + im: self.im * rhs, + } + } +} + +impl core::ops::Sub for Float { + type Output = Complex; + fn sub(self, rhs: Complex) -> Complex { + Complex { + re: self - rhs.re, + im: -rhs.im, + } + } +} + +impl core::ops::Add for Float { + type Output = Complex; + fn add(self, rhs: Complex) -> Complex { + Complex { + re: self + rhs.re, + im: rhs.im, + } + } +} + +impl core::ops::Div for Float { + type Output = Complex; + fn div(self, rhs: Complex) -> Complex { + let denom = rhs.re * rhs.re + rhs.im * rhs.im; + Complex { + re: self * rhs.re / denom, + im: -self * rhs.im / denom, + } + } +} diff --git a/shared/src/utils/containers.rs b/shared/src/utils/containers.rs index bcb2ffa..102e877 100644 --- a/shared/src/utils/containers.rs +++ b/shared/src/utils/containers.rs @@ -4,11 +4,7 @@ use crate::core::geometry::{ use crate::core::pbrt::Float; use crate::utils::Ptr; use crate::utils::math::lerp; -use std::collections::HashSet; -use std::collections::hash_map::RandomState; -use std::hash::{BuildHasher, Hash, Hasher}; -use std::ops::{Add, Index, IndexMut, Mul, Sub}; -use std::sync::{Arc, Mutex, RwLock}; +use core::ops::{Add, Index, IndexMut, Mul, Sub}; pub trait Interpolatable: Copy + Default + Add + Sub + Mul @@ -158,7 +154,7 @@ impl SampledGrid { } pub fn bytes_allocated(&self) -> u32 { - self.values_len * std::mem::size_of::() as u32 + self.values_len * core::mem::size_of::() as u32 } pub fn x_size(&self) -> i32 { diff --git a/shared/src/utils/error.rs b/shared/src/utils/error.rs deleted file mode 100644 index 774f00d..0000000 --- a/shared/src/utils/error.rs +++ /dev/null @@ -1,36 +0,0 @@ -use std::fmt; -use std::sync::Arc; - -#[derive(Debug)] -pub enum LlsError { - SingularMatrix, -} - -impl std::error::Error for LlsError {} - -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum InversionError { - SingularMatrix, - EmptyMatrix, -} - -impl fmt::Display for LlsError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - LlsError::SingularMatrix => write!(f, "Matrix is singular and cannot be inverted."), - } - } -} - -impl fmt::Display for InversionError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - InversionError::SingularMatrix => { - write!(f, "Matrix is singular and cannot be inverted.") - } - InversionError::EmptyMatrix => write!(f, "Matrix is empty and cannot be inverted."), - } - } -} - -impl std::error::Error for InversionError {} diff --git a/shared/src/utils/hash.rs b/shared/src/utils/hash.rs index 56f339f..70dc463 100644 --- a/shared/src/utils/hash.rs +++ b/shared/src/utils/hash.rs @@ -1,6 +1,4 @@ -use crate::core::pbrt::Float; -use std::collections::hash_map::DefaultHasher; -use std::hash::{Hash, Hasher}; +use crate::Float; const U32_TO_F32_SCALE: f32 = 1.0 / 4294967296.0; @@ -58,9 +56,9 @@ pub fn murmur_hash_64a(key: &[u8], seed: u64) -> u64 { } pub fn hash_buffer(data: &T, seed: u64) -> u64 { - let len = std::mem::size_of_val(data); + let len = core::mem::size_of_val(data); let ptr = data as *const T as *const u8; - let bytes = unsafe { std::slice::from_raw_parts(ptr, len) }; + let bytes = unsafe { core::slice::from_raw_parts(ptr, len) }; murmur_hash_64a(bytes, seed) } @@ -72,7 +70,7 @@ macro_rules! hash_values { // We use a temporary tuple or array to pack bits #[repr(C, packed)] struct PackedData { - $(_field: Box<[u8]>),* // Phantom, logic below is simpler + $(_field: Box<[u8]>),* } // Calculate total size and create a byte buffer diff --git a/shared/src/utils/interval.rs b/shared/src/utils/interval.rs index f5ed838..b76733b 100644 --- a/shared/src/utils/interval.rs +++ b/shared/src/utils/interval.rs @@ -1,7 +1,7 @@ use crate::core::pbrt::Float; use crate::utils::math::{next_float_down, next_float_up}; use num_traits::Zero; -use std::ops::{Add, Div, Mul, Neg, Sub}; +use core::ops::{Add, Div, Mul, Neg, Sub}; #[repr(C)] #[derive(Debug, Copy, Clone, PartialEq)] diff --git a/shared/src/utils/math.rs b/shared/src/utils/math.rs index 18cbcfc..0433ee4 100644 --- a/shared/src/utils/math.rs +++ b/shared/src/utils/math.rs @@ -1,4 +1,3 @@ -use super::error::{InversionError, LlsError}; use crate::core::color::{RGB, XYZ}; use crate::core::geometry::{Lerp, MulAdd, Point, Point2f, Point2i, Vector, Vector3f, VectorLike}; use crate::core::pbrt::{Float, FloatBitOps, FloatBits, ONE_MINUS_EPSILON, PI, PI_OVER_4}; @@ -6,13 +5,11 @@ use crate::utils::hash::{hash_buffer, mix_bits}; use crate::utils::sobol::{SOBOL_MATRICES_32, VDC_SOBOL_MATRICES, VDC_SOBOL_MATRICES_INV}; use crate::utils::Ptr; -use half::f16; +use core::fmt::{self, Display, Write}; +use core::iter::{Product, Sum}; +use core::mem; +use core::ops::{Add, Div, Index, IndexMut, Mul, Neg, Rem}; use num_traits::{Float as NumFloat, Num, One, Signed, Zero}; -use std::error::Error; -use std::fmt::{self, Display}; -use std::iter::{Product, Sum}; -use std::mem; -use std::ops::{Add, Div, Index, IndexMut, Mul, Neg, Rem}; #[inline] pub fn degrees(a: Float) -> Float { @@ -69,7 +66,7 @@ pub fn evaluate_polynomial(t: Float, coeffs: &[Float]) -> Float { assert!(!coeffs.is_empty()); let mut result = coeffs[0]; for &c in &coeffs[1..] { - result = t.mul_add(result, c); + result = num_traits::Float::mul_add(t, result, c); } result } @@ -274,13 +271,13 @@ pub fn smooth_step(x: Float, a: Float, b: Float) -> Float { pub fn linear_least_squares( a: [[Float; R]; N], b: [[Float; R]; N], -) -> Result, Box> { +) -> Option> { let am = Matrix::from(a); let bm = Matrix::from(b); let ata = am.transpose() * am; let atb = am.transpose() * bm; let at_ai = ata.inverse()?; - Ok((at_ai * atb).transpose()) + Some((at_ai * atb).transpose()) } pub fn newton_bisection

(mut x0: Float, mut x1: Float, mut f: P) -> Float @@ -473,10 +470,24 @@ pub fn gaussian(x: Float, y: Float, sigma: Float) -> Float { (-(x * x + y * y) / (2. * sigma * sigma)).exp() } +pub fn erf(x: Float) -> Float { + let a1 = 0.254829592; + let a2 = -0.284496736; + let a3 = 1.421413741; + let a4 = -1.453152027; + let a5 = 1.061405429; + let p = 0.3275911; + let sign = if x < 0.0 { -1.0 } else { 1.0 }; + let x = x.abs(); + let t = 1.0 / (1.0 + p * x); + let y = 1.0 - (((((a5 * t + a4) * t) + a3) * t + a2) * t + a1) * t * (-x * x).exp(); + sign * y +} + pub fn gaussian_integral(x0: Float, x1: Float, mu: Float, sigma: Float) -> Float { assert!(sigma > 0.); let sigma_root2 = sigma * 1.414213562373095; - 0.5 * (((mu - x0) / sigma_root2).erf() - ((mu - x1) / sigma_root2).erf()) + 0.5 * (erf((mu - x0) / sigma_root2) - erf((mu - x1) / sigma_root2)) } pub fn sample_linear(u: Float, a: Float, b: Float) -> Float { @@ -1079,7 +1090,7 @@ impl Matrix { where T: Clone + Zero, { - let m: [[T; C]; R] = std::array::from_fn(|_| std::array::from_fn(|_| T::zero())); + let m: [[T; C]; R] = core::array::from_fn(|_| core::array::from_fn(|_| T::zero())); Self { m } } @@ -1189,21 +1200,30 @@ where } } +struct CountingFmt(usize); + +impl Write for CountingFmt { + fn write_str(&mut self, s: &str) -> fmt::Result { + self.0 += s.len(); + Ok(()) + } +} + impl Display for Matrix { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let mut col_widths = [0; C]; + + // Measure without allocation for row in self.m.iter() { for (j, element) in row.iter().enumerate() { - let width = format!("{}", element).len(); - if width > col_widths[j] { - col_widths[j] = width; - } + let mut counter = CountingFmt(0); + let _ = write!(counter, "{}", element); // Ignores errors, just counts + col_widths[j] = col_widths[j].max(counter.0); } } for i in 0..R { write!(f, "[")?; - #[allow(clippy::needless_range_loop)] for j in 0..C { write!(f, "{: >width$} ", self.m[i][j], width = col_widths[j])?; } @@ -1238,8 +1258,8 @@ impl SquareMatrix { T: Copy + Zero + One, { Self { - m: std::array::from_fn(|i| { - std::array::from_fn(|j| if i == j { T::one() } else { T::zero() }) + m: core::array::from_fn(|i| { + core::array::from_fn(|j| if i == j { T::one() } else { T::zero() }) }), } } @@ -1302,9 +1322,9 @@ impl SquareMatrix where T: NumFloat + Sum + Product + Copy, { - pub fn inverse(&self) -> Result { + pub fn inverse(&self) -> Option { if N == 0 { - return Err(InversionError::EmptyMatrix); + return None; } let mut mat = self.m; @@ -1322,7 +1342,7 @@ where let pivot = mat[i][i]; if pivot.is_zero() { - return Err(InversionError::SingularMatrix); + return None; } let inv_pivot = T::one() / pivot; @@ -1344,7 +1364,8 @@ where } } } - Ok(inv) + + Some(inv) } pub fn determinant(&self) -> T { @@ -1448,7 +1469,8 @@ where { type Output = Vector; fn mul(self, rhs: Vector) -> Self::Output { - let arr = std::array::from_fn(|i| self.m[i].iter().zip(&rhs.0).map(|(m, v)| *m * *v).sum()); + let arr = + core::array::from_fn(|i| self.m[i].iter().zip(&rhs.0).map(|(m, v)| *m * *v).sum()); Vector(arr) } } @@ -1459,7 +1481,8 @@ where { type Output = Point; fn mul(self, rhs: Point) -> Self::Output { - let arr = std::array::from_fn(|i| self.m[i].iter().zip(&rhs.0).map(|(m, v)| *m * *v).sum()); + let arr = + core::array::from_fn(|i| self.m[i].iter().zip(&rhs.0).map(|(m, v)| *m * *v).sum()); Point(arr) } } @@ -1540,31 +1563,7 @@ mod tests { } } -#[inline(always)] -pub fn f16_to_f32(bits: u16) -> f32 { - #[cfg(target_os = "cuda")] - { - // Use hardware intrinsic on CUDA - // Cast bits to cuda_std::f16, then cast to f32 - let half_val = unsafe { core::mem::transmute::(bits) }; - half_val.to_f32() - } - - #[cfg(target_arch = "spirv")] - { - // Use shared logic or spirv-std intrinsics if available. - // Sadly, f16 support in rust-gpu is still maturing. - // A manual bit-conversion function is often safest here. - f16_to_f32_software(bits) - } - - #[cfg(not(any(target_os = "cuda", target_arch = "spirv")))] - { - f16::from_bits(bits).to_f32() - } -} - -fn f16_to_f32_software(h: u16) -> f32 { +pub fn f16_to_f32_software(h: u16) -> f32 { let sign = ((h >> 15) & 1) as u32; let exp = ((h >> 10) & 0x1F) as u32; let mant = (h & 0x3FF) as u32; diff --git a/shared/src/utils/mod.rs b/shared/src/utils/mod.rs index f4c62fc..67c5424 100644 --- a/shared/src/utils/mod.rs +++ b/shared/src/utils/mod.rs @@ -1,10 +1,11 @@ +pub mod complex; pub mod containers; -pub mod error; pub mod hash; pub mod interval; pub mod math; pub mod mesh; pub mod noise; +pub mod options; pub mod ptr; pub mod quaternion; pub mod rng; @@ -13,11 +14,12 @@ pub mod sobol; pub mod splines; pub mod transform; +pub use options::PBRTOptions; pub use ptr::Ptr; pub use transform::{AnimatedTransform, Transform, TransformGeneric}; use crate::Float; -use core::sync::atomic::{AtomicU32, AtomicU64, Ordering}; +use core::sync::atomic::{AtomicU32, Ordering}; #[inline] pub fn find_interval(sz: u32, pred: F) -> u32 @@ -113,37 +115,37 @@ impl AtomicFloat { } } -pub struct AtomicDouble { - bits: AtomicU64, -} - -impl AtomicDouble { - pub fn new(val: f64) -> Self { - Self { - bits: AtomicU64::new(val.to_bits()), - } - } - - pub fn get(&self) -> f64 { - f64::from_bits(self.bits.load(Ordering::Relaxed)) - } - - pub fn add(&self, val: f64) { - let mut current_bits = self.bits.load(Ordering::Relaxed); - loop { - let current_val = f64::from_bits(current_bits); - let new_val = current_val + val; - let new_bits = new_val.to_bits(); - - match self.bits.compare_exchange_weak( - current_bits, - new_bits, - Ordering::Relaxed, - Ordering::Relaxed, - ) { - Ok(_) => break, - Err(x) => current_bits = x, - } - } - } -} +// pub struct AtomicDouble { +// bits: AtomicU64, +// } +// +// impl AtomicDouble { +// pub fn new(val: f64) -> Self { +// Self { +// bits: AtomicU64::new(val.to_bits()), +// } +// } +// +// pub fn get(&self) -> f64 { +// f64::from_bits(self.bits.load(Ordering::Relaxed)) +// } +// +// pub fn add(&self, val: f64) { +// let mut current_bits = self.bits.load(Ordering::Relaxed); +// loop { +// let current_val = f64::from_bits(current_bits); +// let new_val = current_val + val; +// let new_bits = new_val.to_bits(); +// +// match self.bits.compare_exchange_weak( +// current_bits, +// new_bits, +// Ordering::Relaxed, +// Ordering::Relaxed, +// ) { +// Ok(_) => break, +// Err(x) => current_bits = x, +// } +// } +// } +// } diff --git a/shared/src/utils/noise.rs b/shared/src/utils/noise.rs index 06dddbe..99c7112 100644 --- a/shared/src/utils/noise.rs +++ b/shared/src/utils/noise.rs @@ -1,6 +1,7 @@ use crate::Float; use crate::core::geometry::{Point3f, Vector3f, VectorLike}; use crate::utils::math::{clamp, lerp, smooth_step}; +use num_traits::Float as NumFloat; static NOISE_PERM_SIZE: usize = 256; static NOISE_PERM: [i32; 2 * NOISE_PERM_SIZE] = [ diff --git a/shared/src/core/options.rs b/shared/src/utils/options.rs similarity index 74% rename from shared/src/core/options.rs rename to shared/src/utils/options.rs index 7235b75..ed46466 100644 --- a/shared/src/core/options.rs +++ b/shared/src/utils/options.rs @@ -1,7 +1,6 @@ -use crate::core::geometry::{Bounds2f, Bounds2i, Point2f, Point2i}; use crate::Float; -use std::ops::Deref; -use std::sync::OnceLock; +use crate::core::geometry::{Bounds2f, Bounds2i, Point2f, Point2i}; +use core::ops::Deref; #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum RenderingCoordinateSystem { @@ -48,17 +47,17 @@ pub struct PBRTOptions { pub basic: BasicPBRTOptions, pub n_threads: usize, - pub log_level: String, + pub log_level: &'static str, pub write_partial_images: bool, - pub image_file: String, + pub image_file: &'static str, pub pixel_samples: Option, pub gpu_device: Option, - pub mse_reference_image: Option, - pub mse_reference_output: Option, + pub mse_reference_image: Option<&'static str>, + pub mse_reference_output: Option<&'static str>, pub debug_start: Option<(Point2i, i32)>, pub quick_render: bool, pub upgrade: bool, - pub display_server: String, + pub display_server: &'static str, pub crop_window: Option, pub pixel_bounds: Option, pub pixel_material: Option, @@ -70,17 +69,17 @@ impl Default for PBRTOptions { Self { basic: BasicPBRTOptions::default(), n_threads: 0, - log_level: "info".to_string(), + log_level: "info", write_partial_images: false, pixel_samples: None, gpu_device: None, quick_render: false, upgrade: false, - image_file: "output.exr".to_string(), + image_file: "output.exr", mse_reference_image: None, mse_reference_output: None, debug_start: Some((Point2i::default(), 0)), - display_server: "".to_string(), + display_server: "", crop_window: None, pixel_bounds: None, pixel_material: None, @@ -96,22 +95,3 @@ impl Deref for PBRTOptions { &self.basic } } - -static OPTIONS: OnceLock = OnceLock::new(); - -pub fn init_pbrt(options: PBRTOptions) { - OPTIONS - .set(options) - .expect("PBRT has already been initialized!"); -} - -pub fn cleanup_pbrt() { - todo!() -} - -pub fn get_options() -> &'static PBRTOptions { - OPTIONS.get().unwrap_or_else(|| { - static DEFAULT: OnceLock = OnceLock::new(); - DEFAULT.get_or_init(PBRTOptions::default) - }) -} diff --git a/shared/src/utils/ptr.rs b/shared/src/utils/ptr.rs index 88115c9..78f5ad2 100644 --- a/shared/src/utils/ptr.rs +++ b/shared/src/utils/ptr.rs @@ -16,7 +16,7 @@ impl Copy for Ptr {} impl PartialEq for Ptr { fn eq(&self, other: &Self) -> bool { - std::ptr::addr_eq(self.ptr, other.ptr) + core::ptr::addr_eq(self.ptr, other.ptr) } } @@ -28,7 +28,7 @@ unsafe impl Sync for Ptr {} impl Ptr { pub const fn null() -> Self { Self { - ptr: std::ptr::null(), + ptr: core::ptr::null(), } } @@ -91,7 +91,7 @@ impl Ptr { if len == 0 { &[] } else { - unsafe { std::slice::from_raw_parts(self.ptr, len) } + unsafe { core::slice::from_raw_parts(self.ptr, len) } } } @@ -101,12 +101,12 @@ impl Ptr { if self.is_null() { if len == 0 { Some(&[]) } else { None } } else { - Some(unsafe { std::slice::from_raw_parts(self.ptr, len) }) + Some(unsafe { core::slice::from_raw_parts(self.ptr, len) }) } } } -impl std::ops::Deref for Ptr { +impl core::ops::Deref for Ptr { type Target = T; #[inline(always)] diff --git a/shared/src/utils/quaternion.rs b/shared/src/utils/quaternion.rs index 3811fb9..ed980f2 100644 --- a/shared/src/utils/quaternion.rs +++ b/shared/src/utils/quaternion.rs @@ -1,5 +1,5 @@ -use std::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Sub, SubAssign}; -use std::ops::{Index, IndexMut}; +use core::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Sub, SubAssign}; +use core::ops::{Index, IndexMut}; use crate::core::geometry::{Vector3f, VectorLike}; use crate::utils::math::{safe_asin, sinx_over_x}; diff --git a/shared/src/utils/sampling.rs b/shared/src/utils/sampling.rs index e88ce75..dffe01a 100644 --- a/shared/src/utils/sampling.rs +++ b/shared/src/utils/sampling.rs @@ -1,8 +1,6 @@ -use crate::check_rare; use crate::core::geometry::{ Bounds2f, Frame, Point2f, Point2i, Point3f, Vector2f, Vector2i, Vector3f, VectorLike, }; -use crate::core::pbrt::{RARE_EVENT_CONDITION_MET, RARE_EVENT_TOTAL_CALLS}; use crate::utils::containers::DeviceArray2D; use crate::utils::find_interval; use crate::utils::math::{ @@ -12,8 +10,12 @@ use crate::utils::math::{ use crate::utils::ptr::Ptr; use crate::utils::rng::Rng; use crate::{Float, INV_2_PI, INV_4_PI, INV_PI, ONE_MINUS_EPSILON, PI, PI_OVER_2, PI_OVER_4}; +use num_traits::Float as NumFloat; use num_traits::Num; +#[cfg(feature = "cpu_debug")] +use crate::{RARE_EVENT_CONDITION_MET, RARE_EVENT_TOTAL_CALLS, check_rare}; + pub fn linear_pdf(x: T, a: T, b: T) -> T where T: Num + Copy + PartialOrd, @@ -267,7 +269,7 @@ pub fn invert_spherical_rectangle_sample( let b0sq = square(b0) + square(b1); let solid_angle_f64: f64 = - g0 as f64 + g1 as f64 + g2 as f64 + g3 as f64 - 2. * std::f64::consts::PI; + g0 as f64 + g1 as f64 + g2 as f64 + g3 as f64 - 2. * core::f64::consts::PI; let solid_angle = solid_angle_f64 as Float; if solid_angle < 1e-3 { @@ -289,6 +291,7 @@ pub fn invert_spherical_rectangle_sample( let invcusq = 1. + z0sq / square(xu); let fusq = invcusq - b0sq; let fu = fusq.sqrt().copysign(xu); + #[cfg(feature = "cpu_debug")] check_rare!(1e-6, fu == 0.); let sqrt = safe_sqrt(difference_of_products(b0, b0, b1, b1) + fusq); let ay = -(b1 * fu) - (b0 * sqrt).copysign(fu * b0); diff --git a/shared/src/utils/transform.rs b/shared/src/utils/transform.rs index a13db3f..9778aa3 100644 --- a/shared/src/utils/transform.rs +++ b/shared/src/utils/transform.rs @@ -1,7 +1,6 @@ +use core::iter::{Product, Sum}; +use core::ops::{Add, Div, Index, IndexMut, Mul}; use num_traits::Float as NumFloat; -use std::iter::{Product, Sum}; -use std::ops::{Add, Div, Index, IndexMut, Mul}; -use std::sync::Arc; use super::math::{SquareMatrix, radians, safe_acos}; use super::quaternion::Quaternion; @@ -13,8 +12,7 @@ use crate::core::geometry::{ use crate::core::interaction::{ Interaction, InteractionBase, InteractionTrait, MediumInteraction, SurfaceInteraction, }; -use crate::core::pbrt::{Float, gamma}; -use crate::utils::error::InversionError; +use crate::{Float, gamma}; #[repr(C)] #[derive(Debug, Copy, Clone)] @@ -28,15 +26,15 @@ impl TransformGeneric { Self { m, m_inv } } - pub fn from_matrix(m: SquareMatrix) -> Result { + pub fn from_matrix(m: SquareMatrix) -> Option { let inv = m.inverse()?; - Ok(Self { m, m_inv: inv }) + Some(Self { m, m_inv: inv }) } - pub fn from_flat(flat: &[T; 16]) -> Result { + pub fn from_flat(flat: &[T; 16]) -> Option { let m: SquareMatrix = SquareMatrix::from(flat); let inv = m.inverse()?; - Ok(Self { m, m_inv: inv }) + Some(Self { m, m_inv: inv }) } pub fn identity() -> Self { @@ -444,11 +442,7 @@ impl TransformGeneric { Self { m, m_inv } } - pub fn perspective( - fov: Float, - n: Float, - f: Float, - ) -> Result, InversionError> { + pub fn perspective(fov: Float, n: Float, f: Float) -> Option> { let persp: SquareMatrix = SquareMatrix::new([ [1., 0., 0., 0.], [0., 1., 0., 0.], @@ -457,7 +451,7 @@ impl TransformGeneric { ]); let inv_tan_ang = 1. / (radians(fov) / 2.).tan(); let persp_transform = TransformGeneric::from_matrix(persp)?; - Ok(TransformGeneric::scale(inv_tan_ang, inv_tan_ang, 1.) * persp_transform) + Some(TransformGeneric::scale(inv_tan_ang, inv_tan_ang, 1.) * persp_transform) } pub fn orthographic(z_near: Float, z_far: Float) -> Self { @@ -1971,11 +1965,11 @@ impl AnimatedTransform { (c1, c2, c3, c4, c5) } else { ( - std::array::from_fn(|_| DerivativeTerm::default()), - std::array::from_fn(|_| DerivativeTerm::default()), - std::array::from_fn(|_| DerivativeTerm::default()), - std::array::from_fn(|_| DerivativeTerm::default()), - std::array::from_fn(|_| DerivativeTerm::default()), + core::array::from_fn(|_| DerivativeTerm::default()), + core::array::from_fn(|_| DerivativeTerm::default()), + core::array::from_fn(|_| DerivativeTerm::default()), + core::array::from_fn(|_| DerivativeTerm::default()), + core::array::from_fn(|_| DerivativeTerm::default()), ) }; AnimatedTransform { @@ -2093,7 +2087,7 @@ pub fn look_at( pos: impl Into, look: impl Into, up: impl Into, -) -> Result, InversionError> { +) -> Option> { let mut world_from_camera: SquareMatrix = SquareMatrix::default(); // Initialize fourth column of viewing matrix let pos: Point3f = pos.into(); @@ -2133,5 +2127,5 @@ pub fn look_at( world_from_camera[3][2] = 0.; let camera_from_world = world_from_camera.inverse()?; - Ok(TransformGeneric::new(camera_from_world, world_from_camera)) + Some(TransformGeneric::new(camera_from_world, world_from_camera)) } diff --git a/src/cameras/mod.rs b/src/cameras/mod.rs new file mode 100644 index 0000000..dbb31f1 --- /dev/null +++ b/src/cameras/mod.rs @@ -0,0 +1,3 @@ +pub mod perspective; +pub mod realistic; +pub mod sherical; diff --git a/src/cameras/perspective.rs b/src/cameras/perspective.rs new file mode 100644 index 0000000..e69de29 diff --git a/src/cameras/realistic.rs b/src/cameras/realistic.rs new file mode 100644 index 0000000..2c329f1 --- /dev/null +++ b/src/cameras/realistic.rs @@ -0,0 +1,90 @@ +use ash::vk::Image; +use shared::cameras::{Mapping, RealisticCamera, realistic::LensElementInterface}; + +struct RealisticCameraData { + aperture_image: Arc, + element_interfaces: Vec, +} + +pub struct RealisticCameraHost { + device: RealisticCamera, + data: RealisticCameraData, +} + +impl RealisticCameraHost { + pub fn new( + base: CameraBase, + lens_params: &[Float], + focus_distance: Float, + set_aperture_diameter: Float, + aperture_image: Arc, + ) -> Self { + let film_ptr = base.film; + if film_ptr.is_null() { + panic!("Camera must have a film"); + } + let film = &*film_ptr; + + let aspect = film.full_resolution().x() as Float / film.full_resolution().y() as Float; + let diagonal = film.diagonal(); + let x = (square(diagonal) / (1.0 + square(diagonal))).sqrt(); + let y = x * aspect; + let physical_extent = + Bounds2f::from_points(Point2f::new(-x / 2., -y / 2.), Point2f::new(x / 2., y / 2.)); + let mut element_interface: Vec = Vec::new(); + + for i in (0..lens_params.len()).step_by(4) { + let curvature_radius = lens_params[i] / 1000.0; + let thickness = lens_params[i + 1] / 1000.0; + let eta = lens_params[i + 2]; + let mut aperture_diameter = lens_params[i + 3] / 1000.0; + + if curvature_radius == 0.0 { + aperture_diameter /= 1000.0; + if set_aperture_diameter > aperture_diameter { + println!("Aperture is larger than possible") + } else { + aperture_diameter = set_aperture_diameter; + } + } + let el_int = LensElementInterface { + curvature_radius, + thickness, + eta, + aperture_radius: aperture_diameter / 2.0, + }; + element_interface.push(el_int); + } + + let half_diag = film.diagonal() / 2.0; + let mut exit_pupil_bounds = [Bounds2f::default(); EXIT_PUPIL_SAMPLES]; + + for i in 0..EXIT_PUPIL_SAMPLES { + let r0 = (i as Float / EXIT_PUPIL_SAMPLES as Float) * half_diag; + let r1 = ((i + 1) as Float / EXIT_PUPIL_SAMPLES as Float) * half_diag; + exit_pupil_bounds[i] = Self::compute_exit_pupil_bounds(&element_interface, r0, r1); + } + + let n_elements = element_interface.len(); + let element_interfaces = element_interface.as_ptr(); + std::mem::forget(element_interface); + + let data = RealisticCameraData { + element_interfaces, + aperture_image, + }; + + let device = RealisticCamera { + base, + focus_distance, + element_interfaces: Ptr::from(element_interfaces), + n_elements, + physical_extent, + set_aperture_diameter, + aperture_image, + exit_pupil_bounds, + }; + + Self { device, data } + } +} diff --git a/src/cameras/spherical.rs b/src/cameras/spherical.rs new file mode 100644 index 0000000..e69de29 diff --git a/src/core/camera.rs b/src/core/camera.rs index b77e1ee..a4a7486 100644 --- a/src/core/camera.rs +++ b/src/core/camera.rs @@ -34,9 +34,9 @@ impl CameraBaseParameters { medium: Arc, params: &ParameterDictionary, loc: &FileLoc, - ) -> Self { - let mut shutter_open = params.get_one_float("shutteropen", 0.); - let mut shutter_close = params.get_one_float("shutterclose", 1.); + ) -> Result { + let mut shutter_open = params.get_one_float("shutteropen", 0.)?; + let mut shutter_close = params.get_one_float("shutterclose", 1.)?; if shutter_close < shutter_open { eprint!( "{}: Shutter close time {} < shutter open {}. Swapping", @@ -44,13 +44,14 @@ impl CameraBaseParameters { ); std::mem::swap(&mut shutter_open, &mut shutter_close); } - CameraBaseParameters { + + Ok(CameraBaseParameters { camera_transform: camera_transform.clone(), shutter_open, shutter_close, film, medium, - } + }) } } @@ -120,14 +121,14 @@ impl CameraFactory for Camera { "perspective" => { let full_res = film.full_resolution(); let camera_params = - CameraBaseParameters::new(camera_transform, film, medium.into(), params, loc); + CameraBaseParameters::new(camera_transform, film, medium.into(), params, loc)?; let base = CameraBase::create(camera_params); - let lens_radius = params.get_one_float("lensradius", 0.); - let focal_distance = params.get_one_float("focaldistance", 1e6); + let lens_radius = params.get_one_float("lensradius", 0.)?; + let focal_distance = params.get_one_float("focaldistance", 1e6)?; let frame = params.get_one_float( "frameaspectratio", full_res.x() as Float / full_res.y() as Float, - ); + )?; let mut screen = if frame > 1. { Bounds2f::from_points(Point2f::new(-frame, -1.), Point2f::new(frame, 1.)) @@ -138,7 +139,7 @@ impl CameraFactory for Camera { ) }; - let sw = params.get_float_array("screenwindow"); + let sw = params.get_float_array("screenwindow")?; if !sw.is_empty() { if get_options().fullscreen { eprint!("Screenwindow is ignored in fullscreen mode"); @@ -157,7 +158,7 @@ impl CameraFactory for Camera { } } - let fov = params.get_one_float("fov", 90.); + let fov = params.get_one_float("fov", 90.)?; let camera = PerspectiveCamera::new(base, fov, screen, lens_radius, focal_distance); arena.alloc(camera); @@ -167,14 +168,14 @@ impl CameraFactory for Camera { "orthographic" => { let full_res = film.full_resolution(); let camera_params = - CameraBaseParameters::new(camera_transform, film, medium.into(), params, loc); + CameraBaseParameters::new(camera_transform, film, medium.into(), params, loc)?; let base = CameraBase::create(camera_params); - let lens_radius = params.get_one_float("lensradius", 0.); - let focal_distance = params.get_one_float("focaldistance", 1e6); + let lens_radius = params.get_one_float("lensradius", 0.)?; + let focal_distance = params.get_one_float("focaldistance", 1e6)?; let frame = params.get_one_float( "frameaspectratio", full_res.x() as Float / full_res.y() as Float, - ); + )?; let mut screen = if frame > 1. { Bounds2f::from_points(Point2f::new(-frame, -1.), Point2f::new(frame, 1.)) @@ -185,7 +186,7 @@ impl CameraFactory for Camera { ) }; - let sw = params.get_float_array("screenwindow"); + let sw = params.get_float_array("screenwindow")?; if !sw.is_empty() { if get_options().fullscreen { eprint!("Screenwindow is ignored in fullscreen mode"); @@ -209,11 +210,11 @@ impl CameraFactory for Camera { } "realistic" => { let camera_params = - CameraBaseParameters::new(camera_transform, film, medium.into(), params, loc); + CameraBaseParameters::new(camera_transform, film, medium.into(), params, loc)?; let base = CameraBase::create(camera_params); - let aperture_diameter = params.get_one_float("aperturediameter", 1.); - let focal_distance = params.get_one_float("focaldistance", 10.); - let lens_file = params.get_one_string("lensfile", ""); + let aperture_diameter = params.get_one_float("aperturediameter", 1.)?; + let focal_distance = params.get_one_float("focaldistance", 10.)?; + let lens_file = params.get_one_string("lensfile", "")?; if lens_file.is_empty() { return Err(anyhow!("{}: No lens file supplied", loc)); @@ -275,7 +276,7 @@ impl CameraFactory for Camera { image }; - let aperture_name = params.get_one_string("aperture", ""); + let aperture_name = params.get_one_string("aperture", "")?; let mut aperture_image: Option = None; if !aperture_name.is_empty() { @@ -389,44 +390,9 @@ impl CameraFactory for Camera { "spherical" => { let full_res = film.full_resolution(); let camera_params = - CameraBaseParameters::new(camera_transform, film, medium.into(), params, loc); + CameraBaseParameters::new(camera_transform, film, medium.into(), params, loc)?; let base = CameraBase::create(camera_params); - let lens_radius = params.get_one_float("lensradius", 0.); - let focal_distance = params.get_one_float("focaldistance", 1e30); - let frame = params.get_one_float( - "frameaspectratio", - full_res.x() as Float / full_res.y() as Float, - ); - - let mut screen = if frame > 1. { - Bounds2f::from_points(Point2f::new(-frame, -1.), Point2f::new(frame, 1.)) - } else { - Bounds2f::from_points( - Point2f::new(-1., -1. / frame), - Point2f::new(1., 1. / frame), - ) - }; - - let sw = params.get_float_array("screenwindow"); - if !sw.is_empty() { - if get_options().fullscreen { - eprint!("Screenwindow is ignored in fullscreen mode"); - } else { - if sw.len() == 4 { - screen = Bounds2f::from_points( - Point2f::new(sw[0], sw[2]), - Point2f::new(sw[1], sw[3]), - ); - } else { - return Err(anyhow!( - "{}: screenwindow param must have four values", - loc - )); - } - } - } - - let m = params.get_one_string("mapping", "equalarea"); + let m = params.get_one_string("mapping", "equalarea")?; let mapping = match m.as_str() { "equalarea" => Mapping::EqualArea, "equirectangular" => Mapping::EquiRectangular, diff --git a/src/core/film.rs b/src/core/film.rs index 2017816..ce291da 100644 --- a/src/core/film.rs +++ b/src/core/film.rs @@ -56,9 +56,9 @@ impl PixelSensor { where Self: Sized, { - let iso = params.get_one_float("iso", 100.); - let mut white_balance_temp = params.get_one_float("whitebalance", 0.); - let sensor_name = params.get_one_string("sensor", "cie1931"); + let iso = params.get_one_float("iso", 100.)?; + let mut white_balance_temp = params.get_one_float("whitebalance", 0.)?; + let sensor_name = params.get_one_string("sensor", "cie1931")?; if sensor_name != "cie1931" && white_balance_temp == 0. { white_balance_temp = 6500.; } @@ -238,7 +238,9 @@ pub trait CreateFilmBase { filter: Filter, sensor: Option<&DevicePixelSensor>, loc: &FileLoc, - ) -> Self; + ) -> Result + where + Self: Sized; } impl CreateFilmBase for FilmBase { @@ -247,9 +249,12 @@ impl CreateFilmBase for FilmBase { filter: Filter, sensor: Option<&DevicePixelSensor>, loc: &FileLoc, - ) -> Self { - let x_res = params.get_one_int("xresolution", 1280); - let y_res = params.get_one_int("yresolution", 720); + ) -> Result + where + Self: Sized, + { + let x_res = params.get_one_int("xresolution", 1280)?; + let y_res = params.get_one_int("yresolution", 720)?; if x_res <= 0 || y_res <= 0 { eprintln!( @@ -259,7 +264,7 @@ impl CreateFilmBase for FilmBase { } let full_resolution = Point2i::new(x_res.max(1), y_res.max(1)); - let crop_data = params.get_float_array("cropwindow"); + let crop_data = params.get_float_array("cropwindow")?; let crop = if crop_data.len() == 4 { Bounds2f::from_points( Point2f::new(crop_data[0], crop_data[2]), @@ -288,16 +293,16 @@ impl CreateFilmBase for FilmBase { let expansion = Point2i::new(rad.x().ceil() as i32, rad.y().ceil() as i32); pixel_bounds = pixel_bounds.expand(expansion); - let diagonal_mm = params.get_one_float("diagonal", 35.0); + let diagonal_mm = params.get_one_float("diagonal", 35.0)?; // let filename = params.get_one_string("filename", "pbrt.exr"); - Self { + Ok(Self { full_resolution, pixel_bounds, filter, diagonal: diagonal_mm * 0.001, sensor: Ptr::from(sensor.unwrap()), - } + }) } } diff --git a/src/core/filter.rs b/src/core/filter.rs index f356d6a..ec7ba5c 100644 --- a/src/core/filter.rs +++ b/src/core/filter.rs @@ -2,53 +2,54 @@ use crate::filters::*; use crate::utils::containers::Array2D; use crate::utils::sampling::PiecewiseConstant2D; use crate::utils::{FileLoc, ParameterDictionary}; +use anyhow::{Result, anyhow}; use shared::Float; use shared::core::filter::{Filter, FilterSampler}; use shared::core::geometry::{Bounds2f, Point2f, Vector2f}; use shared::filters::*; pub trait FilterFactory { - fn create(name: &str, params: &ParameterDictionary, loc: &FileLoc) -> Result; + fn create(name: &str, params: &ParameterDictionary, loc: &FileLoc) -> Result; } impl FilterFactory for Filter { - fn create(name: &str, params: &ParameterDictionary, loc: &FileLoc) -> Result { + fn create(name: &str, params: &ParameterDictionary, loc: &FileLoc) -> Result { match name { "box" => { - let xw = params.get_one_float("xradius", 0.5); - let yw = params.get_one_float("yradius", 0.5); + let xw = params.get_one_float("xradius", 0.5)?; + let yw = params.get_one_float("yradius", 0.5)?; let filter = BoxFilter::new(Vector2f::new(xw, yw)); Ok(Filter::Box(filter)) } "gaussian" => { - let xw = params.get_one_float("xradius", 1.5); - let yw = params.get_one_float("yradius", 1.5); - let sigma = params.get_one_float("sigma", 0.5); + let xw = params.get_one_float("xradius", 1.5)?; + let yw = params.get_one_float("yradius", 1.5)?; + let sigma = params.get_one_float("sigma", 0.5)?; let filter = GaussianFilter::new(Vector2f::new(xw, yw), sigma); Ok(Filter::Gaussian(filter)) } "mitchell" => { - let xw = params.get_one_float("xradius", 2.); - let yw = params.get_one_float("yradius", 2.); - let b = params.get_one_float("B", 1. / 3.); - let c = params.get_one_float("C", 1. / 3.); + let xw = params.get_one_float("xradius", 2.)?; + let yw = params.get_one_float("yradius", 2.)?; + let b = params.get_one_float("B", 1. / 3.)?; + let c = params.get_one_float("C", 1. / 3.)?; let filter = MitchellFilter::new(Vector2f::new(xw, yw), b, c); Ok(Filter::Mitchell(filter)) } "sinc" => { - let xw = params.get_one_float("xradius", 4.); - let yw = params.get_one_float("yradius", 4.); - let tau = params.get_one_float("tau", 3.); + let xw = params.get_one_float("xradius", 4.)?; + let yw = params.get_one_float("yradius", 4.)?; + let tau = params.get_one_float("tau", 3.)?; let filter = LanczosSincFilter::new(Vector2f::new(xw, yw), tau); Ok(Filter::LanczosSinc(filter)) } "triangle" => { - let xw = params.get_one_float("xradius", 2.); - let yw = params.get_one_float("yradius", 2.); + let xw = params.get_one_float("xradius", 2.)?; + let yw = params.get_one_float("yradius", 2.)?; let filter = TriangleFilter::new(Vector2f::new(xw, yw)); Ok(Filter::Triangle(filter)) } - _ => Err(format!("Film type '{}' unknown at {}", name, loc)), + _ => Err(anyhow!("Film type '{}' unknown at {}", name, loc)), } } } diff --git a/src/core/interaction.rs b/src/core/interaction.rs new file mode 100644 index 0000000..3b523f1 --- /dev/null +++ b/src/core/interaction.rs @@ -0,0 +1,71 @@ +use crate::globals::get_options; +use shared::core::interaction::{ + InteractionBase, InteractionTrait, MediumInteraction, SimpleInteraction, SurfaceInteraction, +}; + +pub trait InteractionGetter { + fn get_bsdf( + &mut self, + r: &Ray, + lambda: &SampledWavelengths, + camera: &Camera, + sampler: &mut Sampler, + ) -> Option { + self.compute_differentials(r, camera, sampler.samples_per_pixel() as i32); + + let material = { + let root_mat = self.material; + let mut active_mat: &Material = &*root_mat; + let tex_eval = UniversalTextureEvaluator; + while let Material::Mix(mix) = active_mat { + // We need a context to evaluate the 'amount' texture + let ctx = MaterialEvalContext::from(&*self); + active_mat = mix.choose_material(&tex_eval, &ctx)?; + } + active_mat.clone() + }; + + let ctx = MaterialEvalContext::from(&*self); + let tex_eval = UniversalTextureEvaluator; + let displacement = material.get_displacement(); + let normal_map = Ptr::from(material.get_normal_map().unwrap()); + if !displacement.is_null() || !normal_map.is_null() { + // This calls the function defined above + self.compute_bump_geom(&tex_eval, displacement, normal_map); + } + + let mut bsdf = material.get_bsdf(&tex_eval, &ctx, lambda); + if get_options().force_diffuse { + let r = bsdf.rho_wo(self.common.wo, &[sampler.get1d()], &[sampler.get2d()]); + let diff_bxdf = BxDF::Diffuse(DiffuseBxDF::new(r)); + bsdf = BSDF::new(self.shading.n, self.shading.dpdu, Ptr::from(&diff_bxdf)); + } + Some(bsdf) + } + + fn get_bssrdf( + &self, + _ray: &Ray, + lambda: &SampledWavelengths, + _camera: &Camera, + ) -> Option { + let material = { + let mut active_mat = unsafe { self.material.as_ref() }; + let tex_eval = UniversalTextureEvaluator; + while let Material::Mix(mix) = active_mat { + // We need a context to evaluate the 'amount' texture + let ctx = MaterialEvalContext::from(self); + active_mat = mix.choose_material(&tex_eval, &ctx)?; + } + active_mat.clone() + }; + + let ctx = MaterialEvalContext::from(self); + let tex_eval = UniversalTextureEvaluator; + material.get_bssrdf(&tex_eval, &ctx, lambda) + } +} + +impl InteractionGetter for SurfaceInteraction {} +impl InteractionGetter for MediumInteraction {} +impl InteractionGetter for SimpleInteraction {} diff --git a/src/core/mod.rs b/src/core/mod.rs index 2bddd42..d9b383b 100644 --- a/src/core/mod.rs +++ b/src/core/mod.rs @@ -5,6 +5,7 @@ pub mod color; pub mod film; pub mod filter; pub mod image; +pub mod interaction; pub mod light; pub mod material; pub mod medium; diff --git a/src/core/scene/builder.rs b/src/core/scene/builder.rs index 8fede2a..a9239ab 100644 --- a/src/core/scene/builder.rs +++ b/src/core/scene/builder.rs @@ -4,9 +4,8 @@ use crate::Arena; use crate::spectra::get_colorspace_device; use crate::utils::error::FileLoc; use crate::utils::normalize_utf8; -use crate::utils::parameters::error_exit; use crate::utils::parameters::{ParameterDictionary, ParsedParameterVector}; -use crate::utils::parser::ParserTarget; +use crate::utils::parser::{ParserError, ParserTarget}; use shared::Float; use shared::core::camera::CameraTransform; use shared::core::geometry::Vector3f; @@ -170,26 +169,50 @@ impl BasicSceneBuilder { } } - fn verify_world(&self, name: &str, loc: &FileLoc) { + fn verify_world(&self, name: &str, loc: &FileLoc) -> Result<(), ParserError> { if self.current_block != BlockState::WorldBlock { - log::error!("{}: {} not allowed outside WorldBlock", loc, name); + return Err(ParserError::Generic( + format!("{} not allowed outside WorldBlock", name), + loc.clone(), + )); } + Ok(()) } - fn verify_options(&self, name: &str, loc: &FileLoc) { + fn verify_options(&self, name: &str, loc: &FileLoc) -> Result<(), ParserError> { if self.current_block != BlockState::OptionsBlock { - log::error!("{}: {} not allowed inside WorldBlock", loc, name); + return Err(ParserError::Generic( + format!("{} not allowed inside WorldBlock", name), + loc.clone(), + )); } + Ok(()) + } + + fn make_params( + &self, + params: &ParsedParameterVector, + loc: &FileLoc, + ) -> Result { + ParameterDictionary::new(params.clone(), self.graphics_state.color_space.clone()) + .map_err(|e| ParserError::Generic(format!("parameter error: {}", e), loc.clone())) + } +} + +impl From for ParserError { + fn from(e: anyhow::Error) -> Self { + ParserError::Generic(e.to_string(), FileLoc::default()) } } impl ParserTarget for BasicSceneBuilder { - fn reverse_orientation(&mut self, loc: FileLoc) { - self.verify_world("ReverseOrientation", &loc); + fn reverse_orientation(&mut self, loc: FileLoc) -> Result<(), ParserError> { + self.verify_world("ReverseOrientation", &loc)?; self.graphics_state.reverse_orientation = !self.graphics_state.reverse_orientation; + Ok(()) } - fn color_space(&mut self, name: &str, loc: FileLoc) { + fn color_space(&mut self, name: &str, loc: FileLoc) -> Result<(), ParserError> { let stdcs = get_colorspace_device(); let _ = match stdcs.get_named(name) { Ok(cs) => { @@ -199,25 +222,43 @@ impl ParserTarget for BasicSceneBuilder { eprintln!("Error: Color space '{}' unknown at {}", name, loc); } }; + Ok(()) } - fn identity(&mut self, _loc: FileLoc) { + fn identity(&mut self, _loc: FileLoc) -> Result<(), ParserError> { self.for_active_transforms(|_| Transform::identity()); + Ok(()) } - fn translate(&mut self, dx: Float, dy: Float, dz: Float, _loc: FileLoc) { + fn translate( + &mut self, + dx: Float, + dy: Float, + dz: Float, + _loc: FileLoc, + ) -> Result<(), ParserError> { let t = Transform::translate(Vector3f::new(dx, dy, dz)); self.for_active_transforms(|cur| cur * &t); + Ok(()) } - fn rotate(&mut self, angle: Float, ax: Float, ay: Float, az: Float, _loc: FileLoc) { + fn rotate( + &mut self, + angle: Float, + ax: Float, + ay: Float, + az: Float, + _loc: FileLoc, + ) -> Result<(), ParserError> { let t = Transform::rotate_around_axis(angle, Vector3f::new(ax, ay, az)); self.for_active_transforms(|cur| cur * &t); + Ok(()) } - fn scale(&mut self, sx: Float, sy: Float, sz: Float, _loc: FileLoc) { + fn scale(&mut self, sx: Float, sy: Float, sz: Float, _loc: FileLoc) -> Result<(), ParserError> { let t = Transform::scale(sx, sy, sz); self.for_active_transforms(|cur| cur * &t); + Ok(()) } fn look_at( @@ -232,7 +273,7 @@ impl ParserTarget for BasicSceneBuilder { uy: Float, uz: Float, loc: FileLoc, - ) { + ) -> Result<(), ParserError> { let result = transform::look_at((ex, ey, ez), (lx, ly, lz), (ux, uy, uz)); match result { Ok(t) => { @@ -242,9 +283,10 @@ impl ParserTarget for BasicSceneBuilder { eprintln!("Error: {} at {}", e, loc); } } + Ok(()) } - fn concat_transform(&mut self, m: &[Float; 16], loc: FileLoc) { + fn concat_transform(&mut self, m: &[Float; 16], loc: FileLoc) -> Result<(), ParserError> { let result = Transform::from_flat(m); match result { Ok(t) => { @@ -254,9 +296,10 @@ impl ParserTarget for BasicSceneBuilder { eprintln!("Error: {} at {}", e, loc); } } + Ok(()) } - fn transform(&mut self, m: &[Float; 16], loc: FileLoc) { + fn transform(&mut self, m: &[Float; 16], loc: FileLoc) -> Result<(), ParserError> { let result = Transform::from_flat(m); match result { Ok(t) => { @@ -266,14 +309,16 @@ impl ParserTarget for BasicSceneBuilder { eprintln!("Error: {} at {}", e, loc); } } + Ok(()) } - fn coordinate_system(&mut self, name: &str, _loc: FileLoc) { + fn coordinate_system(&mut self, name: &str, _loc: FileLoc) -> Result<(), ParserError> { self.named_coordinate_systems .insert(name.to_string(), self.graphics_state.ctm); + Ok(()) } - fn coord_sys_transform(&mut self, name: &str, loc: FileLoc) { + fn coord_sys_transform(&mut self, name: &str, loc: FileLoc) -> Result<(), ParserError> { if let Some(saved_ctm) = self.named_coordinate_systems.get(name) { self.graphics_state.ctm = *saved_ctm; } else { @@ -282,10 +327,16 @@ impl ParserTarget for BasicSceneBuilder { name, loc ); } + Ok(()) } - fn camera(&mut self, name: &str, params: &ParsedParameterVector, loc: FileLoc) { - self.verify_options("Camera", &loc); + fn camera( + &mut self, + name: &str, + params: &ParsedParameterVector, + loc: FileLoc, + ) -> Result<(), ParserError> { + self.verify_options("Camera", &loc)?; let camera_from_world = self.graphics_state.ctm; @@ -306,8 +357,7 @@ impl ParserTarget for BasicSceneBuilder { CameraTransform::from_world(animated_world_from_cam, rendering_space); self.render_from_world = camera_from_world.t[0]; - let parameters = - ParameterDictionary::new(params.clone(), self.graphics_state.color_space.clone()); + let parameters = self.make_params(¶ms.clone(), &loc)?; self.current_camera = Some(CameraSceneEntity { base: SceneEntity { @@ -319,85 +369,119 @@ impl ParserTarget for BasicSceneBuilder { medium: self.graphics_state.current_outside_medium.clone(), }); + Ok(()) } - fn active_transform_all(&mut self, _loc: FileLoc) { + fn active_transform_all(&mut self, _loc: FileLoc) -> Result<(), ParserError> { self.graphics_state.active_transform_bits = Self::ALL_TRANSFORM_BITS; + Ok(()) } - fn active_transform_end_time(&mut self, _loc: FileLoc) { + fn active_transform_end_time(&mut self, _loc: FileLoc) -> Result<(), ParserError> { self.graphics_state.active_transform_bits = Self::END_TRANSFORM_BITS; + Ok(()) } - fn active_transform_start_time(&mut self, _loc: FileLoc) { + fn active_transform_start_time(&mut self, _loc: FileLoc) -> Result<(), ParserError> { self.graphics_state.active_transform_bits = Self::START_TRANSFORM_BITS; + Ok(()) } - fn transform_times(&mut self, start: Float, end: Float, loc: FileLoc) { - self.verify_options("TransformTimes", &loc); + fn transform_times( + &mut self, + start: Float, + end: Float, + loc: FileLoc, + ) -> Result<(), ParserError> { + self.verify_options("TransformTimes", &loc)?; self.graphics_state.transform_start_time = start; self.graphics_state.transform_end_time = end; + Ok(()) } - fn option(&mut self, _name: &str, _value: &str, _loc: FileLoc) { + fn option(&mut self, _name: &str, _value: &str, _loc: FileLoc) -> Result<(), ParserError> { todo!() } - fn pixel_filter(&mut self, name: &str, params: &ParsedParameterVector, loc: FileLoc) { - let parameters = - ParameterDictionary::new(params.clone(), self.graphics_state.color_space.clone()); - self.verify_options("PixelFilter", &loc); + fn pixel_filter( + &mut self, + name: &str, + params: &ParsedParameterVector, + loc: FileLoc, + ) -> Result<(), ParserError> { + self.verify_options("PixelFilter", &loc)?; + let parameters = self.make_params(params, &loc)?; self.current_filter = Some(SceneEntity { name: name.to_string(), loc, parameters, }); + Ok(()) } - fn film(&mut self, type_name: &str, params: &ParsedParameterVector, loc: FileLoc) { - let parameters = - ParameterDictionary::new(params.clone(), self.graphics_state.color_space.clone()); - self.verify_options("Film", &loc); + fn film( + &mut self, + type_name: &str, + params: &ParsedParameterVector, + loc: FileLoc, + ) -> Result<(), ParserError> { + self.verify_options("Film", &loc)?; + let parameters = self.make_params(params, &loc)?; self.current_filter = Some(SceneEntity { name: type_name.to_string(), loc, parameters, }); + Ok(()) } - fn accelerator(&mut self, name: &str, params: &ParsedParameterVector, loc: FileLoc) { - let parameters = - ParameterDictionary::new(params.clone(), self.graphics_state.color_space.clone()); - self.verify_options("PixelFilter", &loc); - self.current_filter = Some(SceneEntity { + fn accelerator( + &mut self, + name: &str, + params: &ParsedParameterVector, + loc: FileLoc, + ) -> Result<(), ParserError> { + self.verify_options("Accelerator", &loc)?; + let parameters = self.make_params(params, &loc)?; + self.current_accelerator = Some(SceneEntity { name: name.to_string(), loc, parameters, }); + Ok(()) } - fn integrator(&mut self, name: &str, params: &ParsedParameterVector, loc: FileLoc) { - let parameters = - ParameterDictionary::new(params.clone(), self.graphics_state.color_space.clone()); - self.verify_options("PixelFilter", &loc); - self.current_filter = Some(SceneEntity { + fn integrator( + &mut self, + name: &str, + params: &ParsedParameterVector, + loc: FileLoc, + ) -> Result<(), ParserError> { + self.verify_options("Integrator", &loc)?; + let parameters = self.make_params(params, &loc)?; + self.current_integrator = Some(SceneEntity { name: name.to_string(), loc, parameters, }); + Ok(()) } - fn make_named_medium(&mut self, name: &str, params: &ParsedParameterVector, loc: FileLoc) { + fn make_named_medium( + &mut self, + name: &str, + params: &ParsedParameterVector, + loc: FileLoc, + ) -> Result<(), ParserError> { + self.verify_world("MakeNamedMaterial", &loc)?; let curr_name = normalize_utf8(name); - self.verify_world("MakeNamedMaterial", &loc); if !self.named_material_names.insert(curr_name.to_string()) { - eprintln!("Error: {}: named material '{}' redefined.", loc, name); - return; + return Err(ParserError::Generic( + format!("Named material '{}' redefined.", name), + loc, + )); } - - let parameters = - ParameterDictionary::new(params.clone(), self.graphics_state.color_space.clone()); - + let parameters = self.make_params(params, &loc)?; let entity = SceneEntity { name: name.to_string(), loc, @@ -405,29 +489,41 @@ impl ParserTarget for BasicSceneBuilder { }; self.scene.add_named_material(&curr_name, entity); + Ok(()) } - fn medium_interface(&mut self, inside_name: &str, outside_name: &str, _loc: FileLoc) { + fn medium_interface( + &mut self, + inside_name: &str, + outside_name: &str, + _loc: FileLoc, + ) -> Result<(), ParserError> { let inside = normalize_utf8(inside_name); let outside = normalize_utf8(outside_name); self.graphics_state.current_inside_medium = inside; self.graphics_state.current_outside_medium = outside; + Ok(()) } - fn sampler(&mut self, name: &str, params: &ParsedParameterVector, loc: FileLoc) { - let parameters = - ParameterDictionary::new(params.clone(), self.graphics_state.color_space.clone()); - self.verify_options("Sampler", &loc); + fn sampler( + &mut self, + name: &str, + params: &ParsedParameterVector, + loc: FileLoc, + ) -> Result<(), ParserError> { + self.verify_options("Sampler", &loc)?; + let parameters = self.make_params(¶ms.clone(), &loc)?; self.current_sampler = Some(SceneEntity { name: name.to_string(), loc, parameters, - }) + }); + Ok(()) } - fn world_begin(&mut self, loc: FileLoc, arena: Arc) { - self.verify_options("WorldBegin", &loc); + fn world_begin(&mut self, loc: FileLoc, arena: Arc) -> Result<(), ParserError> { + self.verify_options("WorldBegin", &loc)?; self.current_block = BlockState::WorldBlock; for i in 0..MAX_TRANSFORMS { self.graphics_state.ctm[i] = Transform::default(); @@ -457,23 +553,24 @@ impl ParserTarget for BasicSceneBuilder { .expect("Accelerator not set before WorldBegin"), arena, ); + Ok(()) } - fn attribute_begin(&mut self, loc: FileLoc) { - self.verify_world("AttributeBegin", &loc); + fn attribute_begin(&mut self, loc: FileLoc) -> Result<(), ParserError> { + self.verify_world("AttributeBegin", &loc)?; self.pushed_graphics_states .push(self.graphics_state.clone()); self.push_stack.push(('a', loc)); + Ok(()) } - fn attribute_end(&mut self, loc: FileLoc) { - self.verify_world("AttributeEnd", &loc); + fn attribute_end(&mut self, loc: FileLoc) -> Result<(), ParserError> { + self.verify_world("AttributeEnd", &loc)?; if self.pushed_graphics_states.is_empty() { - log::error!( - "[{:?}] Unmatched AttributeEnd encountered. Ignoring it.", - loc - ); - return; + return Err(ParserError::Generic( + "Unmatched AttributeEnd encountered".into(), + loc, + )); } if let Some(state) = self.pushed_graphics_states.pop() { @@ -482,19 +579,24 @@ impl ParserTarget for BasicSceneBuilder { if let Some((kind, start_loc)) = self.push_stack.pop() { if kind == 'o' { - log::error!( - "[{:?}] Mismatched nesting: open ObjectBegin from {} at AttributeEnd", + return Err(ParserError::Generic( + format!("Mismatched nesting: open ObjectBegin from {}", start_loc), loc, - start_loc - ); - std::process::exit(1); + )); } else { debug_assert_eq!(kind, 'a', "Expected AttributeBegin on the stack"); } } + + Ok(()) } - fn attribute(&mut self, target: &str, params: ParsedParameterVector, loc: FileLoc) { + fn attribute( + &mut self, + target: &str, + params: ParsedParameterVector, + loc: FileLoc, + ) -> Result<(), ParserError> { let current_attributes = match target { "shape" => &mut self.graphics_state.shape_attributes, "light" => &mut self.graphics_state.light_attributes, @@ -502,13 +604,14 @@ impl ParserTarget for BasicSceneBuilder { "medium" => &mut self.graphics_state.medium_attributes, "texture" => &mut self.graphics_state.texture_attributes, _ => { - log::error!( - "[{:?}] Unknown attribute target \"{}\". Must be \"shape\", \"light\", \ + return Err(ParserError::Generic( + format!( + "Unknown attribute target \"{}\". Must be \"shape\", \"light\", \ \"material\", \"medium\", or \"texture\".", + target + ), loc, - target - ); - return; + )); } }; @@ -520,6 +623,7 @@ impl ParserTarget for BasicSceneBuilder { current_attributes.push(p.clone()); } + Ok(()) } fn texture( @@ -530,23 +634,23 @@ impl ParserTarget for BasicSceneBuilder { params: &ParsedParameterVector, loc: FileLoc, arena: Arc, - ) { + ) -> Result<(), ParserError> { let name = normalize_utf8(orig_name); self.verify_world("Texture", &loc); let dict = ParameterDictionary::from_array( params.clone(), &self.graphics_state.texture_attributes, self.graphics_state.color_space.clone(), - ); + )?; if type_name != "float" && type_name != "spectrum" { - error_exit( - Some(&loc), - &format!( + return Err(ParserError::Generic( + format!( "{}: texture type unknown. Must be \"float\" or \"spectrum\".", tex_name ), - ); + loc, + )); } { @@ -557,7 +661,10 @@ impl ParserTarget for BasicSceneBuilder { }; if names.contains(&name) { - error_exit(Some(&loc), &format!("Redefining texture \"{}\".", name)); + return Err(ParserError::Generic( + format!("Redefining texture \"{}\".", name), + loc, + )); } names.insert(name.to_string()); } @@ -579,42 +686,74 @@ impl ParserTarget for BasicSceneBuilder { self.scene .add_spectrum_texture(name.to_string(), entity, arena); } + + Ok(()) } - fn material(&mut self, name: &str, params: &ParsedParameterVector, loc: FileLoc) { + fn material( + &mut self, + name: &str, + params: &ParsedParameterVector, + loc: FileLoc, + ) -> Result<(), ParserError> { self.verify_world("material", &loc); let entity = SceneEntity { name: name.to_string(), loc, - parameters: ParameterDictionary::new(params.clone(), None), + parameters: ParameterDictionary::new(params.clone(), None).unwrap(), }; self.graphics_state.current_material_name = self.scene.add_material(entity).to_string(); + Ok(()) } - fn make_named_material(&mut self, _name: &str, _params: &ParsedParameterVector, _loc: FileLoc) { + fn make_named_material( + &mut self, + _name: &str, + _params: &ParsedParameterVector, + _loc: FileLoc, + ) -> Result<(), ParserError> { todo!() } - fn named_material(&mut self, _name: &str, _loc: FileLoc) { + + fn named_material(&mut self, _name: &str, _loc: FileLoc) -> Result<(), ParserError> { todo!() } - fn light_source(&mut self, _name: &str, _params: &ParsedParameterVector, _loc: FileLoc) { - todo!() + + fn light_source( + &mut self, + _name: &str, + _params: &ParsedParameterVector, + _loc: FileLoc, + ) -> Result<(), ParserError> { + Ok(()) } - fn area_light_source(&mut self, _name: &str, _params: &ParsedParameterVector, _loc: FileLoc) { - todo!() + + fn area_light_source( + &mut self, + _name: &str, + _params: &ParsedParameterVector, + _loc: FileLoc, + ) -> Result<(), ParserError> { + Ok(()) } - fn shape(&mut self, _name: &str, _params: &ParsedParameterVector, _loc: FileLoc) { - todo!() + + fn shape( + &mut self, + _name: &str, + _params: &ParsedParameterVector, + _loc: FileLoc, + ) -> Result<(), ParserError> { + Ok(()) } - fn object_begin(&mut self, _name: &str, _loc: FileLoc) { - todo!() + fn object_begin(&mut self, _name: &str, _loc: FileLoc) -> Result<(), ParserError> { + Ok(()) } - fn object_end(&mut self, _loc: FileLoc) { - todo!() + fn object_end(&mut self, _loc: FileLoc) -> Result<(), ParserError> { + Ok(()) } - fn object_instance(&mut self, _name: &str, _loc: FileLoc) { - todo!() + fn object_instance(&mut self, _name: &str, _loc: FileLoc) -> Result<(), ParserError> { + Ok(()) } - fn end_of_files(&mut self) { - todo!() + fn end_of_files(&mut self) -> Result<(), ParserError> { + Ok(()) } } diff --git a/src/core/scene/scene.rs b/src/core/scene/scene.rs index 2aaa559..ae387cf 100644 --- a/src/core/scene/scene.rs +++ b/src/core/scene/scene.rs @@ -13,6 +13,7 @@ use crate::utils::parallel::{AsyncJob, run_async}; use crate::utils::parameters::{NamedTextures, ParameterDictionary, TextureParameterDictionary}; use crate::utils::{Upload, resolve_filename}; use crate::{Arena, FileLoc}; +use anyhow::{Result, anyhow}; use parking_lot::Mutex; use rayon::prelude::*; use shared::core::camera::Camera; @@ -110,7 +111,7 @@ impl BasicScene { integ: SceneEntity, accel: SceneEntity, arena: Arc, - ) { + ) -> Result<()> { *self.integrator.lock() = Some(integ); *self.accelerator.lock() = Some(accel); @@ -118,9 +119,10 @@ impl BasicScene { *self.film_colorspace.lock() = Some(Arc::clone(cs)); } - let filt = Filter::create(&filter.name, &filter.parameters, &filter.loc); - let shutter_close = camera.base.parameters.get_one_float("shutterclose", 1.); - let shutter_open = camera.base.parameters.get_one_float("shutteropen", 0.); + let filter = Filter::create(&filter.name, &filter.parameters, &filter.loc) + .map_err(|e| anyhow!("Failed to create filter: {}", e))?; + let shutter_close = camera.base.parameters.get_one_float("shutterclose", 1.)?; + let shutter_open = camera.base.parameters.get_one_float("shutteropen", 0.)?; let exposure_time = shutter_close - shutter_open; let film_instance = Arc::new( @@ -128,12 +130,12 @@ impl BasicScene { &film.name, &film.parameters, exposure_time, - filt.expect("Must have a filter"), + filter, Some(camera.camera_transform.clone()), &film.loc, &arena, ) - .expect("Must have a film"), + .map_err(|e| anyhow!("Failed to create film: {}", e))?, ); *self.film_state.lock() = SingletonState { @@ -152,7 +154,7 @@ impl BasicScene { &sampler.loc, &arena_sampler, ) - .expect("Sampler was not correctly created") + .map_err(|e| anyhow!("Failed to create sampler: {}", e)) }); self.sampler_state.lock().job = Some(sampler_job); @@ -170,9 +172,10 @@ impl BasicScene { &camera.base.loc, &arena_camera, ) - .expect("Failed to create camera") + .map_err(|e| anyhow!("Failed to create camera: {}", e)) }); self.camera_state.lock().job = Some(camera_job); + Ok(()) } pub fn add_named_material(&self, name: &str, material: SceneEntity) { @@ -196,7 +199,8 @@ impl BasicScene { get_serial: impl FnOnce(&mut TextureState) -> &mut Vec<(String, TextureSceneEntity)>, get_jobs: impl FnOnce(&mut TextureState) -> &mut HashMap>>, create_fn: F, - ) where + ) -> Result<()> + where T: Send + Sync + 'static, F: FnOnce(TextureSceneEntity) -> T + Send + 'static, { @@ -209,23 +213,24 @@ impl BasicScene { if texture.base.name != "imagemap" && texture.base.name != "ptex" { get_serial(state).push((name, texture)); - return; + return Ok(()); } - let filename = resolve_filename(&texture.base.parameters.get_one_string("filename", "")); + let filename = resolve_filename(&texture.base.parameters.get_one_string("filename", "")?); if !self.validate_texture_file(&filename, &texture.base.loc, &mut state.n_missing_textures) { - return; + return Ok(()); } if state.loading_texture_filenames.contains(&filename) { get_serial(state).push((name, texture)); - return; + return Ok(()); } state.loading_texture_filenames.insert(filename); let job = run_async(move || Arc::new(create_fn(texture))); get_jobs(state).insert(name, job); + Ok(()) } fn validate_texture_file(&self, filename: &str, loc: &FileLoc, n_missing: &mut usize) -> bool { @@ -245,7 +250,12 @@ impl BasicScene { true } - pub fn add_float_texture(&self, name: String, texture: TextureSceneEntity, arena: Arc) { + pub fn add_float_texture( + &self, + name: String, + texture: TextureSceneEntity, + arena: Arc, + ) -> Result<()> { let mut state = self.texture_state.lock(); self.add_texture_generic( name, @@ -265,7 +275,7 @@ impl BasicScene { ) .expect("Could not create Float texture") }, - ); + ) } pub fn add_spectrum_texture( @@ -273,7 +283,7 @@ impl BasicScene { name: String, texture: TextureSceneEntity, arena: Arc, - ) { + ) -> Result<()> { let mut state = self.texture_state.lock(); self.add_texture_generic( name, @@ -294,7 +304,7 @@ impl BasicScene { ) .expect("Could not create spectrum texture") }, - ); + ) } pub fn add_area_light(&self, light: SceneEntity) -> usize { @@ -378,16 +388,25 @@ impl BasicScene { named } + // Assuming that we can carry on if a material is missing. + // This might be a bad idea, but testing it for now (2026/02/19) pub fn create_materials( &self, textures: &NamedTextures, arena: &mut Arena, - ) -> (HashMap, Vec) { + ) -> Result<(HashMap, Vec)> { let mut state = self.material_state.lock(); let finished: Vec<_> = state.normal_map_jobs.drain().collect(); for (filename, job) in finished { - state.normal_maps.insert(filename, job.wait()); + match std::panic::catch_unwind(|| job.wait()) { + Ok(img) => { + state.normal_maps.insert(filename, img); + } + Err(_) => { + log::error!("Failed to load normal map: {}", filename); + } + } } let mut named_materials: HashMap = HashMap::new(); @@ -402,57 +421,72 @@ impl BasicScene { continue; } - let mat_type = entity.parameters.get_one_string("type", ""); + let mat_type = entity.parameters.get_one_string("type", "")?; if mat_type.is_empty() { - log::error!( - "{}: \"string type\" not provided in named material's parameters.", - entity.loc - ); + log::error!("{}: missing material type", entity.loc); continue; } - let normal_map = self.get_normal_map(&state, &entity.parameters); + let normal_map = self.get_normal_map(&state, &entity.parameters)?; let tex_dict = TextureParameterDictionary::new( Arc::new(entity.parameters.clone()), Some(textures), ); - let mat = Material::create( + match Material::create( &mat_type, &tex_dict, normal_map, - &named_materials, // Reference for now + &named_materials, entity.loc.clone(), arena, - ) - .expect("Could not create material"); - - named_materials.insert(name.clone(), mat); + ) { + Ok(mat) => { + named_materials.insert(name.clone(), mat); + } + Err(e) => { + log::error!( + "{}: Failed to create material '{}': {}", + entity.loc, + name, + e + ); + } + } } let materials: Vec = state .materials .iter() - .map(|entity| { - let normal_map = self.get_normal_map(&state, &entity.parameters); - let tex_dict = TextureParameterDictionary::new( - entity.parameters.clone().into(), - Some(textures), - ); + .filter_map(|entity| { + let result: Result = (|| { + let normal_map = self.get_normal_map(&state, &entity.parameters)?; + let tex_dict = TextureParameterDictionary::new( + entity.parameters.clone().into(), + Some(textures), + ); - Material::create( - &entity.name, - &tex_dict, - normal_map, - &named_materials, - entity.loc.clone(), - arena, - ) - .expect("Could not create material") + Material::create( + &entity.name, + &tex_dict, + normal_map, + &named_materials, + entity.loc.clone(), + arena, + ) + })(); + + match result { + Ok(mat) => Some(mat), + Err(e) => { + log::error!("{}: Failed to create material: {}", entity.loc, e); + None + } + } }) .collect(); - (named_materials, materials) + Ok((named_materials, materials)) } pub fn create_aggregate( @@ -499,7 +533,7 @@ impl BasicScene { ) -> Vec> { entities .par_iter() - .map(|sh| { + .filter_map(|sh| { Shape::create( &sh.base.name, *sh.render_from_object.as_ref(), @@ -510,7 +544,7 @@ impl BasicScene { sh.base.loc.clone(), arena, ) - .expect("Could not create shape") + .ok() }) .collect() } @@ -621,56 +655,57 @@ impl BasicScene { Vec::new() } - // ======================================================================== // Getters - // ======================================================================== - pub fn get_camera(&self) -> Arc { + pub fn get_camera(&self) -> Result> { self.get_singleton(&self.camera_state, "Camera") } - pub fn get_sampler(&self) -> Arc { + pub fn get_sampler(&self) -> Result> { self.get_singleton(&self.sampler_state, "Sampler") } - pub fn get_film(&self) -> Arc { + pub fn get_film(&self) -> Result> { self.get_singleton(&self.film_state, "Film") } - // ======================================================================== // Helpers - // ======================================================================== fn get_singleton( &self, state: &Mutex>, name: &str, - ) -> Arc { + ) -> Result> { let mut guard = state.lock(); if let Some(ref res) = guard.result { - return res.clone(); + return Ok(res.clone()); } if let Some(job) = guard.job.take() { - let res = Arc::new(job.wait()); + let val = job.wait()?; + let res = Arc::new(val); guard.result = Some(res.clone()); - return res; + return Ok(res); } - panic!("{} requested but not initialized!", name); + Err(anyhow!("{} requested but not initialized!", name)) } - fn start_loading_normal_maps(&self, state: &mut MaterialState, params: &ParameterDictionary) { - let filename = resolve_filename(¶ms.get_one_string("normalmap", "")); + fn start_loading_normal_maps( + &self, + state: &mut MaterialState, + params: &ParameterDictionary, + ) -> Result<()> { + let filename = resolve_filename(¶ms.get_one_string("normalmap", "")?); if filename.is_empty() { - return; + return Ok(()); } if state.normal_map_jobs.contains_key(&filename) || state.normal_maps.contains_key(&filename) { - return; + return Ok(()); } let filename_clone = filename.clone(); @@ -693,18 +728,19 @@ impl BasicScene { }); state.normal_map_jobs.insert(filename, job); + Ok(()) } fn get_normal_map( &self, state: &MaterialState, params: &ParameterDictionary, - ) -> Option> { - let filename = resolve_filename(¶ms.get_one_string("normalmap", "")); + ) -> Result>> { + let filename = resolve_filename(¶ms.get_one_string("normalmap", "")?); if filename.is_empty() { - return None; + return Ok(None); } - state.normal_maps.get(&filename).cloned() + Ok(state.normal_maps.get(&filename).cloned()) } fn get_alpha_texture( diff --git a/src/core/scene/state.rs b/src/core/scene/state.rs index f7f3745..70300f7 100644 --- a/src/core/scene/state.rs +++ b/src/core/scene/state.rs @@ -2,6 +2,7 @@ use super::{SceneEntity, TextureSceneEntity}; use crate::core::image::Image; use crate::core::texture::{FloatTexture, SpectrumTexture}; use crate::utils::parallel::AsyncJob; +use anyhow::Result; use shared::core::light::Light; use shared::core::medium::Medium; use std::collections::{HashMap, HashSet}; @@ -41,7 +42,7 @@ pub struct MediaState { #[derive(Debug)] pub struct SingletonState { pub result: Option>, - pub job: Option>, + pub job: Option>>, } impl Default for SingletonState { diff --git a/src/core/texture.rs b/src/core/texture.rs index f7ad55f..2349856 100644 --- a/src/core/texture.rs +++ b/src/core/texture.rs @@ -165,7 +165,9 @@ pub trait CreateTextureMapping { params: &TextureParameterDictionary, render_from_texture: &Transform, loc: &FileLoc, - ) -> Self; + ) -> Result + where + Self: Sized; } impl CreateTextureMapping for TextureMapping2D { @@ -173,36 +175,39 @@ impl CreateTextureMapping for TextureMapping2D { params: &TextureParameterDictionary, render_from_texture: &Transform, loc: &FileLoc, - ) -> Self { - let mtype = params.get_one_string("mapping", "uv"); + ) -> Result + where + Self: Sized, + { + let mtype = params.get_one_string("mapping", "uv")?; match mtype.as_str() { "uv" => { - let su = params.get_one_float("uscale", 1.); - let sv = params.get_one_float("vscale", 1.); - let du = params.get_one_float("udelta", 0.); - let dv = params.get_one_float("vdelta", 0.); + let su = params.get_one_float("uscale", 1.)?; + let sv = params.get_one_float("vscale", 1.)?; + let du = params.get_one_float("udelta", 0.)?; + let dv = params.get_one_float("vdelta", 0.)?; let mapping = UVMapping::new(su, sv, du, dv); - TextureMapping2D::UV(mapping) + Ok(TextureMapping2D::UV(mapping)) } "spherical" => { let mapping = SphericalMapping::new(&render_from_texture.inverse()); - TextureMapping2D::Spherical(mapping) + Ok(TextureMapping2D::Spherical(mapping)) } "cylindrical" => { let mapping = CylindricalMapping::new(&render_from_texture.inverse()); - TextureMapping2D::Cylindrical(mapping) + Ok(TextureMapping2D::Cylindrical(mapping)) } "planar" => { - let vs = params.get_one_vector3f("v1", Vector3f::new(1., 0., 0.)); - let vt = params.get_one_vector3f("v2", Vector3f::new(0., 1., 0.)); - let ds = params.get_one_float("udelta", 0.); - let dt = params.get_one_float("vdelta", 0.); + let vs = params.get_one_vector3f("v1", Vector3f::new(1., 0., 0.))?; + let vt = params.get_one_vector3f("v2", Vector3f::new(0., 1., 0.))?; + let ds = params.get_one_float("udelta", 0.)?; + let dt = params.get_one_float("vdelta", 0.)?; let mapping = PlanarMapping::new(&render_from_texture.inverse(), vs, vt, ds, dt); - TextureMapping2D::Planar(mapping) + Ok(TextureMapping2D::Planar(mapping)) } _ => { log::error!("{}: 2D texture mapping unknown {}", loc, mtype); - TextureMapping2D::UV(UVMapping::default()) + Ok(TextureMapping2D::UV(UVMapping::default())) } } } diff --git a/src/films/gbuffer.rs b/src/films/gbuffer.rs index 00ff578..dd74009 100644 --- a/src/films/gbuffer.rs +++ b/src/films/gbuffer.rs @@ -57,17 +57,17 @@ impl CreateFilm for GBufferFilm { _arena: &Arena, ) -> Result { let colorspace = params.color_space.as_ref().unwrap(); - let max_component_value = params.get_one_float("maxcomponentvalue", Float::INFINITY); - let write_fp16 = params.get_one_bool("savefp16", true); + let max_component_value = params.get_one_float("maxcomponentvalue", Float::INFINITY)?; + let write_fp16 = params.get_one_bool("savefp16", true)?; let sensor = PixelSensor::create(params, colorspace.clone(), exposure_time, loc)?; - let film_base = FilmBase::create(params, filter, Some(&sensor.device()), loc); + let film_base = FilmBase::create(params, filter, Some(&sensor.device()), loc)?; - let filename = params.get_one_string("filename", "pbrt.exr"); + let filename = params.get_one_string("filename", "pbrt.exr")?; if Path::new(&filename).extension() != Some("exr".as_ref()) { return Err(anyhow!("{}: EXR is the only format supported by GBufferFilm", loc).into()); } - let coords_system = params.get_one_string("coordinatesystem", "camera"); + let coords_system = params.get_one_string("coordinatesystem", "camera")?; let mut apply_inverse = false; let camera_transform = camera_transform.ok_or_else(|| anyhow!("GBufferFilm requires a camera_transform"))?; diff --git a/src/films/rgb.rs b/src/films/rgb.rs index 2a55da6..fb8b20b 100644 --- a/src/films/rgb.rs +++ b/src/films/rgb.rs @@ -70,10 +70,10 @@ impl CreateFilm for RGBFilm { _arena: &Arena, ) -> Result { let colorspace = params.color_space.as_ref().unwrap(); - let max_component_value = params.get_one_float("maxcomponentvalue", Float::INFINITY); - let write_fp16 = params.get_one_bool("savefp16", true); + let max_component_value = params.get_one_float("maxcomponentvalue", Float::INFINITY)?; + let write_fp16 = params.get_one_bool("savefp16", true)?; let sensor = PixelSensor::create(params, colorspace.clone(), exposure_time, loc)?; - let film_base = FilmBase::create(params, filter, Some(&sensor.device()), loc); + let film_base = FilmBase::create(params, filter, Some(&sensor.device()), loc)?; let film = RGBFilmHost::new(film_base, &colorspace, max_component_value, write_fp16); Ok(Film::RGB(film.device)) } diff --git a/src/films/spectral.rs b/src/films/spectral.rs index 749ec47..ea30234 100644 --- a/src/films/spectral.rs +++ b/src/films/spectral.rs @@ -97,19 +97,19 @@ impl CreateFilm for SpectralFilm { _arena: &Arena, ) -> Result { let colorspace = params.color_space.as_ref().unwrap(); - let max_component_value = params.get_one_float("maxcomponentvalue", Float::INFINITY); - let write_fp16 = params.get_one_bool("savefp16", true); + let max_component_value = params.get_one_float("maxcomponentvalue", Float::INFINITY)?; + let write_fp16 = params.get_one_bool("savefp16", true)?; let sensor = PixelSensor::create(params, colorspace.clone(), exposure_time, loc)?; - let film_base = FilmBase::create(params, filter, Some(&sensor.device()), loc); + let film_base = FilmBase::create(params, filter, Some(&sensor.device()), loc)?; - let filename = params.get_one_string("filename", "pbrt.exr"); + let filename = params.get_one_string("filename", "pbrt.exr")?; if Path::new(&filename).extension() != Some("exr".as_ref()) { return Err(anyhow!("{}: EXR is the only format supported by GBufferFilm", loc).into()); } - let n_buckets = params.get_one_int("nbuckets", 16) as usize; - let lambda_min = params.get_one_float("lambdamin", LAMBDA_MIN as Float); - let lambda_max = params.get_one_float("lambdamin", LAMBDA_MAX as Float); + let n_buckets = params.get_one_int("nbuckets", 16)? as usize; + let lambda_min = params.get_one_float("lambdamin", LAMBDA_MIN as Float)?; + let lambda_max = params.get_one_float("lambdamin", LAMBDA_MAX as Float)?; if lambda_min < LAMBDA_MIN as Float && lambda_max > LAMBDA_MAX as Float { return Err(anyhow!( "{}: PBRT must be recompiled with different values of LAMBDA_MIN and LAMBDA_MAX", diff --git a/src/filters/lanczos.rs b/src/filters/lanczos.rs index 54cd070..14f1a11 100644 --- a/src/filters/lanczos.rs +++ b/src/filters/lanczos.rs @@ -15,10 +15,32 @@ impl LanczosFilterCreator for LanczosSincFilter { windowed_sinc(p.x(), radius.x(), tau) * windowed_sinc(p.y(), radius.y(), tau) }); + let sqrt_samples = 64; + let n_samples = sqrt_samples * sqrt_samples; + let area = (2.0 * self.radius.x()) * (2.0 * self.radius.y()); + let mut sum = 0.0; + let mut rng = rand::rng(); + + for y in 0..sqrt_samples { + for x in 0..sqrt_samples { + let u = Point2f::new( + (x as Float + rng.random::()) / sqrt_samples as Float, + (y as Float + rng.random::()) / sqrt_samples as Float, + ); + let p = Point2f::new( + lerp(u.x(), -self.radius.x(), self.radius.x()), + lerp(u.y(), -self.radius.y(), self.radius.y()), + ); + sum += self.evaluate(p); + } + } + let integral = sum / n_samples as Float * area; + Self { radius, tau, sampler, + integral, } } } diff --git a/src/globals.rs b/src/globals.rs index d68eb0a..78c94d8 100644 --- a/src/globals.rs +++ b/src/globals.rs @@ -2,6 +2,23 @@ use crate::core::color::RGBToSpectrumTableData; use bytemuck::cast_slice; use once_cell::sync::Lazy; use shared::Float; +use shared::PBRTOptions; +use std::sync::OnceLock; + +static OPTIONS: OnceLock = OnceLock::new(); + +pub fn init_pbrt(options: PBRTOptions) { + OPTIONS + .set(options) + .expect("PBRT has already been initialized!"); +} + +pub fn get_options() -> &'static PBRTOptions { + OPTIONS.get().unwrap_or_else(|| { + static DEFAULT: OnceLock = OnceLock::new(); + DEFAULT.get_or_init(PBRTOptions::default) + }) +} static SRGB_SCALE_BYTES: &[u8] = include_bytes!("../data/srgb_scale.dat"); static SRGB_COEFFS_BYTES: &[u8] = include_bytes!("../data/srgb_coeffs.dat"); diff --git a/src/lib.rs b/src/lib.rs index 0bbb0b7..8c0cf49 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,6 @@ +#![feature(f16)] #[allow(dead_code)] +pub mod cameras; pub mod core; pub mod films; pub mod filters; diff --git a/src/lights/diffuse.rs b/src/lights/diffuse.rs index f12cfb6..21b7374 100644 --- a/src/lights/diffuse.rs +++ b/src/lights/diffuse.rs @@ -114,10 +114,10 @@ impl CreateLight for DiffuseAreaLight { ) -> Result { let mut l = params.get_one_spectrum("l", None, SpectrumType::Illuminant); let illum_spec = Spectrum::Dense(colorspace.unwrap().illuminant); - let mut scale = params.get_one_float("scale", 1.); - let two_sided = params.get_one_bool("twosided", false); + let mut scale = params.get_one_float("scale", 1.)?; + let two_sided = params.get_one_bool("twosided", false)?; - let filename = resolve_filename(¶ms.get_one_string("filename", "")); + let filename = resolve_filename(¶ms.get_one_string("filename", "")?); let (image, image_color_space) = if !filename.is_empty() { if l.is_some() { return Err(anyhow!("{}: both \"L\" and \"filename\" specified", loc)); @@ -151,7 +151,7 @@ impl CreateLight for DiffuseAreaLight { let l_for_scale = l.as_ref().unwrap_or(&illum_spec); scale /= spectrum_to_photometric(*l_for_scale); - let phi_v = params.get_one_float("power", -1.0); + let phi_v = params.get_one_float("power", -1.0)?; if phi_v > 0.0 { // k_e is the emissive power of the light as defined by the spectral // distribution and texture and is used to normalize the emitted diff --git a/src/lights/distant.rs b/src/lights/distant.rs index d0f8fb1..5dc67cd 100644 --- a/src/lights/distant.rs +++ b/src/lights/distant.rs @@ -54,10 +54,10 @@ impl CreateLight for DistantLight { SpectrumType::Illuminant, ) .unwrap(); - let mut scale = parameters.get_one_float("scale", 1.); + let mut scale = parameters.get_one_float("scale", 1.)?; - let from = parameters.get_one_point3f("from", Point3f::new(0., 0., 0.)); - let to = parameters.get_one_point3f("to", Point3f::new(0., 0., 1.)); + let from = parameters.get_one_point3f("from", Point3f::new(0., 0., 0.))?; + let to = parameters.get_one_point3f("to", Point3f::new(0., 0., 1.))?; let w = (from - to).normalize(); let (v1, v2) = w.coordinate_system(); let m: [Float; 16] = [ @@ -82,7 +82,7 @@ impl CreateLight for DistantLight { let final_render = render_from_light * t; scale /= spectrum_to_photometric(l); // Adjust scale to meet target illuminance value - let e_v = parameters.get_one_float("illuminance", -1.); + let e_v = parameters.get_one_float("illuminance", -1.)?; if e_v > 0. { scale *= e_v; } diff --git a/src/lights/goniometric.rs b/src/lights/goniometric.rs index 1d66095..ddc57cd 100644 --- a/src/lights/goniometric.rs +++ b/src/lights/goniometric.rs @@ -72,8 +72,8 @@ impl CreateLight for GoniometricLight { SpectrumType::Illuminant, ) .expect("Could not retrieve spectrum"); - let mut scale = params.get_one_float("scale", 1.); - let filename = resolve_filename(¶ms.get_one_string("filename", "")); + let mut scale = params.get_one_float("scale", 1.)?; + let filename = resolve_filename(¶ms.get_one_string("filename", "")?); let image: Ptr = if filename.is_empty() { Ptr::null() } else { @@ -102,7 +102,7 @@ impl CreateLight for GoniometricLight { }; scale /= spectrum_to_photometric(i); - let phi_v = params.get_one_float("power", -1.0); + let phi_v = params.get_one_float("power", -1.0)?; if phi_v > 0.0 { let k_e = compute_emissive_power(&image); diff --git a/src/lights/infinite.rs b/src/lights/infinite.rs index 578949d..fef0422 100644 --- a/src/lights/infinite.rs +++ b/src/lights/infinite.rs @@ -132,10 +132,10 @@ pub fn create( arena: &Arena, ) -> Result { let l = parameters.get_spectrum_array("L", SpectrumType::Illuminant); - let mut scale = parameters.get_one_float("scale", 1.0); - let portal = parameters.get_point3f_array("portal"); - let filename = resolve_filename(¶meters.get_one_string("filename", "")); - let e_v = parameters.get_one_float("illuminance", -1.0); + let mut scale = parameters.get_one_float("scale", 1.0)?; + let portal = parameters.get_point3f_array("portal")?; + let filename = resolve_filename(¶meters.get_one_string("filename", "")?); + let e_v = parameters.get_one_float("illuminance", -1.0)?; let has_spectrum = !l.is_empty(); let has_file = !filename.is_empty(); diff --git a/src/lights/point.rs b/src/lights/point.rs index 8bddecd..5875b9d 100644 --- a/src/lights/point.rs +++ b/src/lights/point.rs @@ -60,15 +60,15 @@ impl CreateLight for PointLight { SpectrumType::Illuminant, ) .unwrap(); - let mut scale = parameters.get_one_float("scale", 1.); + let mut scale = parameters.get_one_float("scale", 1.)?; scale /= spectrum_to_photometric(l); - let phi_v = parameters.get_one_float("power", 1.); + let phi_v = parameters.get_one_float("power", 1.)?; if phi_v > 0. { let k_e = 4. * PI; scale *= phi_v / k_e; } - let from = parameters.get_one_point3f("from", Point3f::zero()); + let from = parameters.get_one_point3f("from", Point3f::zero())?; let tf = Transform::translate(from.into()); let final_render = render_from_light * tf; let specific = PointLight::new(final_render, medium.into(), l, scale); diff --git a/src/lights/projection.rs b/src/lights/projection.rs index f195a4a..2e63831 100644 --- a/src/lights/projection.rs +++ b/src/lights/projection.rs @@ -104,11 +104,11 @@ impl CreateLight for ProjectionLight { _colorspace: Option<&RGBColorSpace>, arena: &Arena, ) -> Result { - let mut scale = parameters.get_one_float("scale", 1.); - let power = parameters.get_one_float("power", -1.); - let fov = parameters.get_one_float("fov", 90.); + let mut scale = parameters.get_one_float("scale", 1.)?; + let power = parameters.get_one_float("power", -1.)?; + let fov = parameters.get_one_float("fov", 90.)?; - let filename = resolve_filename(¶meters.get_one_string("filename", "")); + let filename = resolve_filename(¶meters.get_one_string("filename", "")?); if filename.is_empty() { return Err(anyhow!( "{}: must provide filename for projection light", diff --git a/src/lights/spot.rs b/src/lights/spot.rs index 4115e43..20607de 100644 --- a/src/lights/spot.rs +++ b/src/lights/spot.rs @@ -1,4 +1,3 @@ -// use crate::core::image::{Image, ImageIO, ImageMetadata}; use crate::core::light::{CreateLight, lookup_spectrum}; use crate::core::spectrum::spectrum_to_photometric; use crate::core::texture::FloatTexture; @@ -72,17 +71,17 @@ impl CreateLight for SpotLight { SpectrumType::Illuminant, ) .expect("No spectrum"); - let mut scale = parameters.get_one_float("scale", 1.); - let coneangle = parameters.get_one_float("coneangle", 30.); - let conedelta = parameters.get_one_float("conedelta", 5.); - let from = parameters.get_one_point3f("from", Point3f::zero()); - let to = parameters.get_one_point3f("to", Point3f::new(0., 0., 1.)); + let mut scale = parameters.get_one_float("scale", 1.)?; + let coneangle = parameters.get_one_float("coneangle", 30.)?; + let conedelta = parameters.get_one_float("conedelta", 5.)?; + let from = parameters.get_one_point3f("from", Point3f::zero())?; + let to = parameters.get_one_point3f("to", Point3f::new(0., 0., 1.))?; let dir_to_z = Transform::from(Frame::from_z((to - from).normalize())); let t = Transform::translate(from.into()) * dir_to_z.inverse(); let final_render = render_from_light * t; scale /= spectrum_to_photometric(i); - let phi_v = parameters.get_one_float("power", -1.); + let phi_v = parameters.get_one_float("power", -1.)?; if phi_v > 0. { let cos_falloff_end = radians(coneangle).cos(); let cos_falloff_start = radians(coneangle - conedelta).cos(); diff --git a/src/materials/coated.rs b/src/materials/coated.rs index 60f6fb6..3762001 100644 --- a/src/materials/coated.rs +++ b/src/materials/coated.rs @@ -2,7 +2,7 @@ use crate::core::image::Image; use crate::core::material::CreateMaterial; use crate::core::texture::SpectrumTexture; use crate::spectra::data::get_named_spectrum; -use crate::utils::{Arena, FileLoc, TextureParameterDictionary, Upload, parameters::error_exit}; +use crate::utils::{Arena, FileLoc, TextureParameterDictionary, Upload}; use anyhow::{Result, bail}; use shared::core::material::Material; use shared::core::spectrum::Spectrum; diff --git a/src/shapes/triangle.rs b/src/shapes/triangle.rs index b6fcfff..65865f5 100644 --- a/src/shapes/triangle.rs +++ b/src/shapes/triangle.rs @@ -2,7 +2,7 @@ use crate::core::shape::{ALL_TRIANGLE_MESHES, CreateShape}; use crate::core::texture::FloatTexture; use crate::shapes::mesh::TriangleMesh; use crate::utils::{Arena, FileLoc, ParameterDictionary}; -use anyhow::{Result, anyhow}; +use anyhow::{Result, bail}; use log::warn; use shared::core::shape::Shape; use shared::shapes::TriangleShape; @@ -29,9 +29,7 @@ impl CreateShape for TriangleShape { if vertex_indices.is_empty() { if p.len() == 3 { } else { - return Err(anyhow!( - "Vertex indices \"indices\" must be provided with triangle mesh." - )); + return bail!("Vertex indices \"indices\" must be provided with triangle mesh."); } } else if vertex_indices.len() % 3 != 0 { let excess = vertex_indices.len() % 3; @@ -45,9 +43,7 @@ impl CreateShape for TriangleShape { } if p.is_empty() { - return Err(anyhow!( - "Vertex positions \"P\" must be provided with triangle mesh." - )); + return bail!("Vertex positions \"P\" must be provided with triangle mesh."); } if !uvs.is_empty() && uvs.len() != p.len() { @@ -68,11 +64,11 @@ impl CreateShape for TriangleShape { for (_, &index) in vertex_indices.iter().enumerate() { // Check for negative indices (if keeping i32) or out of bounds if index < 0 || index as usize >= p.len() { - return Err(anyhow!( + return bail!( "TriangleMesh has out-of-bounds vertex index {} ({} \"P\" values were given). Discarding this mesh.", index, p.len() - )); + ); } } diff --git a/src/utils/backend.rs b/src/utils/backend.rs index ec7c166..b9654d1 100644 --- a/src/utils/backend.rs +++ b/src/utils/backend.rs @@ -12,7 +12,7 @@ pub struct SystemAllocator; impl Default for SystemAllocator { fn default() -> Self { - Self + Self {} } } @@ -35,72 +35,130 @@ impl GpuAllocator for SystemAllocator { /// CUDA unified memory backend using CudaAllocator #[cfg(feature = "cuda")] -pub struct CudaAllocator; +pub mod cuda { + use super::GpuAllocator; + use std::alloc::Layout; -#[cfg(feature = "cuda")] -impl Default for CudaAllocator { - fn default() -> Self { - Self + pub struct CudaAllocator; + + impl Default for CudaAllocator { + fn default() -> Self { + Self {} + } } -} -#[cfg(feature = "cuda")] -impl GpuAllocator for CudaAllocator { - unsafe fn alloc(&self, layout: Layout) -> *mut u8 { - use cust::memory::cuda_malloc_unified; + impl GpuAllocator for CudaAllocator { + unsafe fn alloc(&self, layout: Layout) -> *mut u8 { + use cust::memory::cuda_malloc_unified; + use cust_raw::driver_sys::*; - let size = layout.size().max(layout.align()); - if size == 0 { - return layout.align() as *mut u8; + let size = layout.size().max(layout.align()); + if size == 0 { + return layout.align() as *mut u8; + } + + let mut ctx: CUcontext = std::ptr::null_mut(); + cuCtxGetCurrent(&mut ctx); + if ctx.is_null() { + let mut primary: CUcontext = std::ptr::null_mut(); + cuDevicePrimaryCtxRetain(&mut primary, 0); + cuCtxSetCurrent(primary); + } + + let mut unified_ptr = + unsafe { cuda_malloc_unified::(size).expect("cuda_malloc_unified failed") }; + let raw = unified_ptr.as_raw_mut(); + std::mem::forget(unified_ptr); + raw } - let mut unified_ptr = - unsafe { cuda_malloc_unified::(size).expect("cuda_malloc_unified failed") }; - let raw = unified_ptr.as_raw_mut(); - std::mem::forget(unified_ptr); - raw - } - - unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) { - use cust::memory::{UnifiedPointer, cuda_free_unified}; - if layout.size() > 0 { - let _ = unsafe { cuda_free_unified(UnifiedPointer::wrap(ptr)) }; + unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) { + use cust::memory::{UnifiedPointer, cuda_free_unified}; + if layout.size() > 0 { + let _ = unsafe { cuda_free_unified(UnifiedPointer::wrap(ptr)) }; + } } } } -/// Vulkan backend using gpu-allocator. +/// Vulkan backend (gpu-allocator for now, there might be a better solution) #[cfg(feature = "vulkan")] pub mod vulkan { use super::GpuAllocator; use ash::vk; use gpu_allocator::MemoryLocation; - use gpu_allocator::vulkan::{Allocation, AllocationCreateDesc, AllocationScheme, Allocator}; + use gpu_allocator::vulkan::{ + Allocation, AllocationCreateDesc, AllocationScheme, Allocator, AllocatorCreateDesc, + }; use parking_lot::Mutex; use std::alloc::Layout; use std::collections::HashMap; + use std::sync::OnceLock; - pub struct VulkanAllocator { - allocator: Mutex, - /// Track pointer -> Allocation so we can free later. - allocations: Mutex>, + // So, having a static allocator seems like a terrible idea + // But I cant find a way to get a functioning generic Arena constructor + // That might not even be a necessity, since rust-gpu/rust-cuda might actually handle that + // differently + static VK_ALLOCATOR: OnceLock = OnceLock::new(); + + struct VulkanAllocatorInner { + state: Mutex, } - impl VulkanAllocator { - pub fn new(allocator: Allocator) -> Self { - Self { - allocator: Mutex::new(allocator), - allocations: Mutex::new(HashMap::new()), + struct VulkanState { + allocator: Allocator, + allocations: HashMap, + } + + pub fn init_vulkan( + instance: &ash::Instance, + device: &ash::Device, + physical_device: vk::PhysicalDevice, + ) { + VK_ALLOCATOR.get_or_init(|| { + let allocator = Allocator::new(&AllocatorCreateDesc { + instance: instance.clone(), + device: device.clone(), + physical_device, + debug_settings: Default::default(), + buffer_device_address: false, + allocation_sizes: Default::default(), + }) + .expect("Failed to create Vulkan allocator"); + + VulkanAllocatorInner { + state: Mutex::new(VulkanState { + allocator, + allocations: HashMap::new(), + }), } - } + }); + } + + fn inner() -> &'static VulkanAllocatorInner { + VK_ALLOCATOR + .get() + .expect("Vulkan not initialized — call init_vulkan() before Arena::default()") } impl Default for VulkanAllocator { fn default() -> Self { + let _ = inner(); Self } } + pub struct VulkanAllocator; + + // impl VulkanAllocator { + // pub fn new(allocator: Allocator) -> Self { + // Self { + // allocator: Mutex::new(allocator), + // allocations: Mutex::new(HashMap::new()), + // } + // } + // } + impl GpuAllocator for VulkanAllocator { unsafe fn alloc(&self, layout: Layout) -> *mut u8 { let size = layout.size().max(layout.align()); @@ -108,8 +166,10 @@ pub mod vulkan { return layout.align() as *mut u8; } - let mut alloc = self.allocator.lock(); - let allocation = alloc + let inner = inner(); + let mut state = inner.state.lock(); + let allocation = state + .allocator .allocate(&AllocationCreateDesc { name: "arena", requirements: vk::MemoryRequirements { @@ -128,7 +188,7 @@ pub mod vulkan { .expect("Vulkan allocation not host-mapped") .as_ptr() as *mut u8; - self.allocations.lock().insert(ptr as usize, allocation); + state.allocations.insert(ptr as usize, allocation); ptr } @@ -136,9 +196,11 @@ pub mod vulkan { if layout.size() == 0 { return; } - if let Some(allocation) = self.allocations.lock().remove(&(ptr as usize)) { - self.allocator - .lock() + let inner = inner(); + let mut state = inner.state.lock(); + if let Some(allocation) = state.allocations.remove(&(ptr as usize)) { + state + .allocator .free(allocation) .expect("Vulkan free failed"); } diff --git a/src/utils/math.rs b/src/utils/math.rs index a0ca81e..92cbfaf 100644 --- a/src/utils/math.rs +++ b/src/utils/math.rs @@ -1,6 +1,33 @@ +use half::f16; use shared::Float; use shared::utils::hash::hash_buffer; -use shared::utils::math::{DeviceDigitPermutation, PRIMES, permutation_element}; +use shared::utils::math::{ + DeviceDigitPermutation, PRIMES, f16_to_f32_software, permutation_element, +}; + +#[inline(always)] +pub fn f16_to_f32(bits: u16) -> f32 { + #[cfg(target_os = "cuda")] + { + // Use hardware intrinsic on CUDA + // Cast bits to cuda_f16, then cast to f32 + let half_val = unsafe { core::mem::transmute::(bits) }; + half_val.to_f32() + } + + #[cfg(target_arch = "spirv")] + { + // Use shared logic or spirv-std intrinsics if available. + // Sadly, f16 support in rust-gpu is still maturing. + // A manual bit-conversion function is often safest here. + f16_to_f32_software(bits) + } + + #[cfg(not(any(target_os = "cuda", target_arch = "spirv")))] + { + f16::from_bits(bits).to_f32() + } +} pub struct DigitPermutation { pub permutations: Vec, @@ -68,13 +95,7 @@ pub fn compute_radical_inverse_permutations(seed: u64) -> (Vec, Vec; #[cfg(all(feature = "cuda", not(feature = "vulkan")))] -pub type Arena = arena::Arena; +pub type Arena = arena::Arena; #[cfg(not(any(feature = "cuda", feature = "vulkan")))] pub type Arena = arena::Arena; diff --git a/src/utils/parser.rs b/src/utils/parser.rs index 81d35de..943e441 100644 --- a/src/utils/parser.rs +++ b/src/utils/parser.rs @@ -1,3 +1,4 @@ +use anyhow::Result; use flate2::read::GzDecoder; use memmap2::Mmap; use std::collections::HashMap; @@ -12,10 +13,23 @@ use crate::utils::parameters::{ParameterDictionary, ParsedParameter, ParsedParam use shared::Float; pub trait ParserTarget { - fn identity(&mut self, loc: FileLoc); - fn translate(&mut self, dx: Float, dy: Float, dz: Float, loc: FileLoc); - fn rotate(&mut self, angle: Float, ax: Float, ay: Float, az: Float, loc: FileLoc); - fn scale(&mut self, sx: Float, sy: Float, sz: Float, loc: FileLoc); + fn identity(&mut self, loc: FileLoc) -> Result<(), ParserError>; + fn translate( + &mut self, + dx: Float, + dy: Float, + dz: Float, + loc: FileLoc, + ) -> Result<(), ParserError>; + fn rotate( + &mut self, + angle: Float, + ax: Float, + ay: Float, + az: Float, + loc: FileLoc, + ) -> Result<(), ParserError>; + fn scale(&mut self, sx: Float, sy: Float, sz: Float, loc: FileLoc) -> Result<(), ParserError>; fn look_at( &mut self, ex: Float, @@ -28,32 +42,85 @@ pub trait ParserTarget { uy: Float, uz: Float, loc: FileLoc, - ); - fn transform(&mut self, transform: &[Float; 16], loc: FileLoc); - fn concat_transform(&mut self, transform: &[Float; 16], loc: FileLoc); + ) -> Result<(), ParserError>; + fn transform(&mut self, transform: &[Float; 16], loc: FileLoc) -> Result<(), ParserError>; + fn concat_transform( + &mut self, + transform: &[Float; 16], + loc: FileLoc, + ) -> Result<(), ParserError>; + fn coordinate_system(&mut self, name: &str, loc: FileLoc) -> Result<(), ParserError>; + fn coord_sys_transform(&mut self, name: &str, loc: FileLoc) -> Result<(), ParserError>; + fn active_transform_all(&mut self, loc: FileLoc) -> Result<(), ParserError>; + fn active_transform_end_time(&mut self, loc: FileLoc) -> Result<(), ParserError>; + fn active_transform_start_time(&mut self, loc: FileLoc) -> Result<(), ParserError>; + fn transform_times( + &mut self, + start: Float, + end: Float, + loc: FileLoc, + ) -> Result<(), ParserError>; - fn coordinate_system(&mut self, name: &str, loc: FileLoc); - fn coord_sys_transform(&mut self, name: &str, loc: FileLoc); - fn active_transform_all(&mut self, loc: FileLoc); - fn active_transform_end_time(&mut self, loc: FileLoc); - fn active_transform_start_time(&mut self, loc: FileLoc); - fn transform_times(&mut self, start: Float, end: Float, loc: FileLoc); + fn option(&mut self, name: &str, value: &str, loc: FileLoc) -> Result<(), ParserError>; + fn color_space(&mut self, n: &str, loc: FileLoc) -> Result<(), ParserError>; + fn pixel_filter( + &mut self, + name: &str, + params: &ParsedParameterVector, + loc: FileLoc, + ) -> Result<(), ParserError>; + fn film( + &mut self, + type_name: &str, + params: &ParsedParameterVector, + loc: FileLoc, + ) -> Result<(), ParserError>; + fn accelerator( + &mut self, + name: &str, + params: &ParsedParameterVector, + loc: FileLoc, + ) -> Result<(), ParserError>; + fn integrator( + &mut self, + name: &str, + params: &ParsedParameterVector, + loc: FileLoc, + ) -> Result<(), ParserError>; + fn camera( + &mut self, + name: &str, + params: &ParsedParameterVector, + loc: FileLoc, + ) -> Result<(), ParserError>; + fn make_named_medium( + &mut self, + name: &str, + params: &ParsedParameterVector, + loc: FileLoc, + ) -> Result<(), ParserError>; + fn medium_interface( + &mut self, + inside_name: &str, + outside_name: &str, + loc: FileLoc, + ) -> Result<(), ParserError>; + fn sampler( + &mut self, + name: &str, + params: &ParsedParameterVector, + loc: FileLoc, + ) -> Result<(), ParserError>; - fn option(&mut self, name: &str, value: &str, loc: FileLoc); - fn color_space(&mut self, n: &str, loc: FileLoc); - fn pixel_filter(&mut self, name: &str, params: &ParsedParameterVector, loc: FileLoc); - fn film(&mut self, type_name: &str, params: &ParsedParameterVector, loc: FileLoc); - fn accelerator(&mut self, name: &str, params: &ParsedParameterVector, loc: FileLoc); - fn integrator(&mut self, name: &str, params: &ParsedParameterVector, loc: FileLoc); - fn camera(&mut self, name: &str, params: &ParsedParameterVector, loc: FileLoc); - fn make_named_medium(&mut self, name: &str, params: &ParsedParameterVector, loc: FileLoc); - fn medium_interface(&mut self, inside_name: &str, outside_name: &str, loc: FileLoc); - fn sampler(&mut self, name: &str, params: &ParsedParameterVector, loc: FileLoc); - - fn world_begin(&mut self, loc: FileLoc, arena: Arc); - fn attribute_begin(&mut self, loc: FileLoc); - fn attribute_end(&mut self, loc: FileLoc); - fn attribute(&mut self, target: &str, params: ParsedParameterVector, loc: FileLoc); + fn world_begin(&mut self, loc: FileLoc, arena: Arc) -> Result<(), ParserError>; + fn attribute_begin(&mut self, loc: FileLoc) -> Result<(), ParserError>; + fn attribute_end(&mut self, loc: FileLoc) -> Result<(), ParserError>; + fn attribute( + &mut self, + target: &str, + params: ParsedParameterVector, + loc: FileLoc, + ) -> Result<(), ParserError>; fn texture( &mut self, @@ -63,22 +130,47 @@ pub trait ParserTarget { params: &ParsedParameterVector, loc: FileLoc, arena: Arc, - ); - fn material(&mut self, name: &str, params: &ParsedParameterVector, loc: FileLoc); - fn make_named_material(&mut self, name: &str, params: &ParsedParameterVector, loc: FileLoc); - fn named_material(&mut self, name: &str, loc: FileLoc); + ) -> Result<(), ParserError>; + fn material( + &mut self, + name: &str, + params: &ParsedParameterVector, + loc: FileLoc, + ) -> Result<(), ParserError>; + fn make_named_material( + &mut self, + name: &str, + params: &ParsedParameterVector, + loc: FileLoc, + ) -> Result<(), ParserError>; + fn named_material(&mut self, name: &str, loc: FileLoc) -> Result<(), ParserError>; - fn light_source(&mut self, name: &str, params: &ParsedParameterVector, loc: FileLoc); - fn area_light_source(&mut self, name: &str, params: &ParsedParameterVector, loc: FileLoc); + fn light_source( + &mut self, + name: &str, + params: &ParsedParameterVector, + loc: FileLoc, + ) -> Result<(), ParserError>; + fn area_light_source( + &mut self, + name: &str, + params: &ParsedParameterVector, + loc: FileLoc, + ) -> Result<(), ParserError>; - fn shape(&mut self, name: &str, params: &ParsedParameterVector, loc: FileLoc); - fn reverse_orientation(&mut self, loc: FileLoc); + fn shape( + &mut self, + name: &str, + params: &ParsedParameterVector, + loc: FileLoc, + ) -> Result<(), ParserError>; + fn reverse_orientation(&mut self, loc: FileLoc) -> Result<(), ParserError>; - fn object_begin(&mut self, name: &str, loc: FileLoc); - fn object_end(&mut self, loc: FileLoc); - fn object_instance(&mut self, name: &str, loc: FileLoc); + fn object_begin(&mut self, name: &str, loc: FileLoc) -> Result<(), ParserError>; + fn object_end(&mut self, loc: FileLoc) -> Result<(), ParserError>; + fn object_instance(&mut self, name: &str, loc: FileLoc) -> Result<(), ParserError>; - fn end_of_files(&mut self); + fn end_of_files(&mut self) -> Result<(), ParserError>; } #[derive(Debug, Clone)] @@ -394,24 +486,42 @@ impl FormattingParserTarget { } impl ParserTarget for FormattingParserTarget { - fn option(&mut self, name: &str, value: &str, _loc: FileLoc) { + fn option(&mut self, name: &str, value: &str, _loc: FileLoc) -> Result<(), ParserError> { println!("{}Option \"{}\" \"{}\"", self.indent(0), name, value); + Ok(()) } - fn identity(&mut self, _loc: FileLoc) { + fn identity(&mut self, _loc: FileLoc) -> Result<(), ParserError> { println!("{}Identity", self.indent(0)); + Ok(()) } - fn translate(&mut self, dx: Float, dy: Float, dz: Float, _loc: FileLoc) { + fn translate( + &mut self, + dx: Float, + dy: Float, + dz: Float, + _loc: FileLoc, + ) -> Result<(), ParserError> { println!("{}Translate {} {} {}", self.indent(0), dx, dy, dz); + Ok(()) } - fn rotate(&mut self, angle: Float, ax: Float, ay: Float, az: Float, _loc: FileLoc) { - println!("{}Rotate {} {} {} {}", self.indent(0), angle, ax, ay, az); + fn rotate( + &mut self, + angle: Float, + ax: Float, + ay: Float, + az: Float, + _loc: FileLoc, + ) -> Result<(), ParserError> { + println!("{} Rotate {} {} {} {}", self.indent(0), angle, ax, ay, az); + Ok(()) } - fn scale(&mut self, sx: Float, sy: Float, sz: Float, _loc: FileLoc) { + fn scale(&mut self, sx: Float, sy: Float, sz: Float, _loc: FileLoc) -> Result<(), ParserError> { println!("{}Scale {} {} {}", self.indent(0), sx, sy, sz); + Ok(()) } fn look_at( @@ -426,7 +536,7 @@ impl ParserTarget for FormattingParserTarget { uy: Float, uz: Float, _loc: FileLoc, - ) { + ) -> Result<(), ParserError> { println!( "{}LookAt {} {} {} {} {} {} {} {} {}", self.indent(0), @@ -440,56 +550,75 @@ impl ParserTarget for FormattingParserTarget { uy, uz ); + Ok(()) } - fn concat_transform(&mut self, t: &[Float; 16], _loc: FileLoc) { - // Rust arrays verify size at compile time, simpler than C++ pointers + fn concat_transform(&mut self, t: &[Float; 16], _loc: FileLoc) -> Result<(), ParserError> { println!("{}ConcatTransform [ {:?} ]", self.indent(0), t); + Ok(()) } - fn transform(&mut self, t: &[Float; 16], _loc: FileLoc) { + fn transform(&mut self, t: &[Float; 16], _loc: FileLoc) -> Result<(), ParserError> { println!("{}Transform [ {:?} ]", self.indent(0), t); + Ok(()) } - fn coordinate_system(&mut self, name: &str, _loc: FileLoc) { + fn coordinate_system(&mut self, name: &str, _loc: FileLoc) -> Result<(), ParserError> { println!("{}CoordinateSystem \"{}\"", self.indent(0), name); + Ok(()) } - fn coord_sys_transform(&mut self, name: &str, _loc: FileLoc) { + fn coord_sys_transform(&mut self, name: &str, _loc: FileLoc) -> Result<(), ParserError> { println!("{}CoordSysTransform \"{}\"", self.indent(0), name); + Ok(()) } - fn world_begin(&mut self, _loc: FileLoc, _arena: Arc) { + fn world_begin(&mut self, _loc: FileLoc, _arena: Arc) -> Result<(), ParserError> { println!("{}WorldBegin", self.indent(0)); self.cat_indent_count += 4; + Ok(()) } - fn attribute_begin(&mut self, _loc: FileLoc) { + fn attribute_begin(&mut self, _loc: FileLoc) -> Result<(), ParserError> { println!("{}AttributeBegin", self.indent(0)); self.cat_indent_count += 4; + Ok(()) } - fn attribute_end(&mut self, _loc: FileLoc) { + fn attribute_end(&mut self, _loc: FileLoc) -> Result<(), ParserError> { self.cat_indent_count = self.cat_indent_count.saturating_sub(4); println!("{}AttributeEnd", self.indent(0)); + Ok(()) } - fn shape(&mut self, name: &str, params: &ParsedParameterVector, _loc: FileLoc) { + fn shape( + &mut self, + name: &str, + params: &ParsedParameterVector, + _loc: FileLoc, + ) -> Result<(), ParserError> { println!( "{}Shape \"{}\" ... ({} params)", self.indent(0), name, params.len() ); + Ok(()) } - fn material(&mut self, name: &str, params: &ParsedParameterVector, _loc: FileLoc) { + fn material( + &mut self, + name: &str, + params: &ParsedParameterVector, + _loc: FileLoc, + ) -> Result<(), ParserError> { println!( "{}Material \"{}\" ... ({} params)", self.indent(0), name, params.len() ); + Ok(()) } fn texture( @@ -500,7 +629,7 @@ impl ParserTarget for FormattingParserTarget { _params: &ParsedParameterVector, _loc: FileLoc, _arena: Arc, - ) { + ) -> Result<(), ParserError> { println!( "{}Texture \"{}\" \"{}\" \"{}\"", self.indent(0), @@ -508,38 +637,137 @@ impl ParserTarget for FormattingParserTarget { type_name, tex_name ); + Ok(()) } - fn active_transform_all(&mut self, _loc: FileLoc) {} - fn active_transform_end_time(&mut self, _loc: FileLoc) {} - fn active_transform_start_time(&mut self, _loc: FileLoc) {} - fn transform_times(&mut self, _s: Float, _e: Float, _loc: FileLoc) {} - fn color_space(&mut self, _n: &str, _loc: FileLoc) {} - fn pixel_filter(&mut self, _n: &str, _p: &ParsedParameterVector, _loc: FileLoc) {} - fn film(&mut self, _t: &str, _p: &ParsedParameterVector, _loc: FileLoc) {} - fn accelerator(&mut self, _n: &str, _p: &ParsedParameterVector, _loc: FileLoc) {} - fn integrator(&mut self, _n: &str, _p: &ParsedParameterVector, _loc: FileLoc) {} - fn camera(&mut self, _n: &str, _p: &ParsedParameterVector, _loc: FileLoc) {} - fn make_named_medium(&mut self, _n: &str, _p: &ParsedParameterVector, _loc: FileLoc) {} - fn medium_interface(&mut self, _i: &str, _o: &str, _loc: FileLoc) {} - fn sampler(&mut self, _n: &str, _p: &ParsedParameterVector, _loc: FileLoc) {} - fn attribute(&mut self, _t: &str, _p: ParsedParameterVector, _loc: FileLoc) {} - fn make_named_material(&mut self, _n: &str, _p: &ParsedParameterVector, _loc: FileLoc) {} - fn named_material(&mut self, _n: &str, _loc: FileLoc) {} - fn light_source(&mut self, _n: &str, _p: &ParsedParameterVector, _loc: FileLoc) {} - fn area_light_source(&mut self, _n: &str, _p: &ParsedParameterVector, _loc: FileLoc) {} - fn reverse_orientation(&mut self, _loc: FileLoc) {} - fn object_begin(&mut self, name: &str, _loc: FileLoc) { + fn active_transform_all(&mut self, _loc: FileLoc) -> Result<(), ParserError> { + Ok(()) + } + fn active_transform_end_time(&mut self, _loc: FileLoc) -> Result<(), ParserError> { + Ok(()) + } + fn active_transform_start_time(&mut self, _loc: FileLoc) -> Result<(), ParserError> { + Ok(()) + } + fn transform_times(&mut self, _s: Float, _e: Float, _loc: FileLoc) -> Result<(), ParserError> { + Ok(()) + } + fn color_space(&mut self, _n: &str, _loc: FileLoc) -> Result<(), ParserError> { + Ok(()) + } + fn pixel_filter( + &mut self, + _n: &str, + _p: &ParsedParameterVector, + _loc: FileLoc, + ) -> Result<(), ParserError> { + Ok(()) + } + fn film( + &mut self, + _t: &str, + _p: &ParsedParameterVector, + _loc: FileLoc, + ) -> Result<(), ParserError> { + Ok(()) + } + fn accelerator( + &mut self, + _n: &str, + _p: &ParsedParameterVector, + _loc: FileLoc, + ) -> Result<(), ParserError> { + Ok(()) + } + fn integrator( + &mut self, + _n: &str, + _p: &ParsedParameterVector, + _loc: FileLoc, + ) -> Result<(), ParserError> { + Ok(()) + } + fn camera( + &mut self, + _n: &str, + _p: &ParsedParameterVector, + _loc: FileLoc, + ) -> Result<(), ParserError> { + Ok(()) + } + fn make_named_medium( + &mut self, + _n: &str, + _p: &ParsedParameterVector, + _loc: FileLoc, + ) -> Result<(), ParserError> { + Ok(()) + } + fn medium_interface(&mut self, _i: &str, _o: &str, _loc: FileLoc) -> Result<(), ParserError> { + Ok(()) + } + fn sampler( + &mut self, + _n: &str, + _p: &ParsedParameterVector, + _loc: FileLoc, + ) -> Result<(), ParserError> { + Ok(()) + } + fn attribute( + &mut self, + _t: &str, + _p: ParsedParameterVector, + _loc: FileLoc, + ) -> Result<(), ParserError> { + Ok(()) + } + fn make_named_material( + &mut self, + _n: &str, + _p: &ParsedParameterVector, + _loc: FileLoc, + ) -> Result<(), ParserError> { + Ok(()) + } + fn named_material(&mut self, _n: &str, _loc: FileLoc) -> Result<(), ParserError> { + Ok(()) + } + fn light_source( + &mut self, + _n: &str, + _p: &ParsedParameterVector, + _loc: FileLoc, + ) -> Result<(), ParserError> { + Ok(()) + } + fn area_light_source( + &mut self, + _n: &str, + _p: &ParsedParameterVector, + _loc: FileLoc, + ) -> Result<(), ParserError> { + Ok(()) + } + fn reverse_orientation(&mut self, _loc: FileLoc) -> Result<(), ParserError> { + Ok(()) + } + fn object_begin(&mut self, name: &str, _loc: FileLoc) -> Result<(), ParserError> { println!("{}ObjectBegin \"{}\"", self.indent(0), name); self.cat_indent_count += 4; + Ok(()) } - fn object_end(&mut self, _loc: FileLoc) { + fn object_end(&mut self, _loc: FileLoc) -> Result<(), ParserError> { self.cat_indent_count = self.cat_indent_count.saturating_sub(4); println!("{}ObjectEnd", self.indent(0)); + Ok(()) } - fn object_instance(&mut self, _n: &str, _loc: FileLoc) {} - fn end_of_files(&mut self) { + fn object_instance(&mut self, _n: &str, _loc: FileLoc) -> Result<(), ParserError> { + Ok(()) + } + fn end_of_files(&mut self) -> Result<(), ParserError> { self.cat_indent_count = 0; + Ok(()) } } @@ -756,17 +984,17 @@ impl<'a> SceneParser<'a> { match first_char { 'A' => match token.text.as_str() { - "AttributeBegin" => self.target.attribute_begin(token.loc), - "AttributeEnd" => self.target.attribute_end(token.loc), + "AttributeBegin" => self.target.attribute_begin(token.loc)?, + "AttributeEnd" => self.target.attribute_end(token.loc)?, "Attribute" => { self.parse_basic_entry(|t, n, p, l| t.attribute(n, p.to_vec(), l))? } "ActiveTransform" => { let a = self.next_token_required()?; match a.text.as_str() { - "All" => self.target.active_transform_all(token.loc), - "EndTime" => self.target.active_transform_end_time(token.loc), - "StartTime" => self.target.active_transform_start_time(token.loc), + "All" => self.target.active_transform_all(token.loc)?, + "EndTime" => self.target.active_transform_end_time(token.loc)?, + "StartTime" => self.target.active_transform_start_time(token.loc)?, _ => { return Err(ParserError::Generic( "Unknown ActiveTransform type".into(), @@ -796,19 +1024,19 @@ impl<'a> SceneParser<'a> { m[i] = self.expect_float()?; } self.expect_token("]")?; - self.target.concat_transform(&m, token.loc); + self.target.concat_transform(&m, token.loc)?; } "CoordinateSystem" => { let n = self.expect_quoted_string()?; - self.target.coordinate_system(&n, token.loc); + self.target.coordinate_system(&n, token.loc)?; } "CoordSysTransform" => { let n = self.expect_quoted_string()?; - self.target.coord_sys_transform(&n, token.loc); + self.target.coord_sys_transform(&n, token.loc)?; } "ColorSpace" => { let n = self.expect_quoted_string()?; - self.target.color_space(&n, token.loc); + self.target.color_space(&n, token.loc)?; } _ => { return Err(ParserError::Generic( @@ -846,7 +1074,7 @@ impl<'a> SceneParser<'a> { self.file_stack.push(new_tokenizer); } - "Identity" => self.target.identity(token.loc), + "Identity" => self.target.identity(token.loc)?, _ => { return Err(ParserError::Generic( format!("Unknown directive {}", token.text), @@ -892,7 +1120,7 @@ impl<'a> SceneParser<'a> { self.unget(next); inside.clone() }; - self.target.medium_interface(&inside, &outside, token.loc); + self.target.medium_interface(&inside, &outside, token.loc)?; } _ => { return Err(ParserError::Generic( @@ -905,7 +1133,7 @@ impl<'a> SceneParser<'a> { 'N' => match token.text.as_str() { "NamedMaterial" => { let n = self.expect_quoted_string()?; - self.target.named_material(&n, token.loc); + self.target.named_material(&n, token.loc)?; } _ => { return Err(ParserError::Generic( @@ -918,18 +1146,18 @@ impl<'a> SceneParser<'a> { 'O' => match token.text.as_str() { "ObjectBegin" => { let n = self.expect_quoted_string()?; - self.target.object_begin(&n, token.loc); + self.target.object_begin(&n, token.loc)?; } - "ObjectEnd" => self.target.object_end(token.loc), + "ObjectEnd" => self.target.object_end(token.loc)?, "ObjectInstance" => { let n = self.expect_quoted_string()?; - self.target.object_instance(&n, token.loc); + self.target.object_instance(&n, token.loc)?; } "Option" => { let name = self.expect_quoted_string()?; let val_tok = self.next_token_required()?; let val = val_tok.dequote().to_string(); - self.target.option(&name, &val, token.loc); + self.target.option(&name, &val, token.loc)?; } _ => { return Err(ParserError::Generic( @@ -952,7 +1180,7 @@ impl<'a> SceneParser<'a> { }, 'R' => match token.text.as_str() { - "ReverseOrientation" => self.target.reverse_orientation(token.loc), + "ReverseOrientation" => self.target.reverse_orientation(token.loc)?, "Rotate" => { let angle = self.expect_float()?; let ax = self.expect_float()?; @@ -986,8 +1214,8 @@ impl<'a> SceneParser<'a> { }, 'T' => match token.text.as_str() { - "TransformBegin" => self.target.attribute_begin(token.loc), - "TransformEnd" => self.target.attribute_end(token.loc), + "TransformBegin" => self.target.attribute_begin(token.loc)?, + "TransformEnd" => self.target.attribute_end(token.loc)?, "Transform" => { self.expect_token("[")?; let mut m = [0.0; 16]; @@ -995,18 +1223,18 @@ impl<'a> SceneParser<'a> { m[i] = self.expect_float()?; } self.expect_token("]")?; - self.target.transform(&m, token.loc); + self.target.transform(&m, token.loc)?; } "Translate" => { let x = self.expect_float()?; let y = self.expect_float()?; let z = self.expect_float()?; - self.target.translate(x, y, z, token.loc); + self.target.translate(x, y, z, token.loc)?; } "TransformTimes" => { let s = self.expect_float()?; let e = self.expect_float()?; - self.target.transform_times(s, e, token.loc); + self.target.transform_times(s, e, token.loc)?; } "Texture" => { let name = self.expect_quoted_string()?; @@ -1020,7 +1248,7 @@ impl<'a> SceneParser<'a> { ¶ms, token.loc, arena.clone(), - ); + )?; } _ => { return Err(ParserError::Generic( @@ -1031,7 +1259,7 @@ impl<'a> SceneParser<'a> { }, 'W' => match token.text.as_str() { - "WorldBegin" => self.target.world_begin(token.loc, arena.clone()), + "WorldBegin" => self.target.world_begin(token.loc, arena.clone())?, "WorldEnd" => {} _ => { return Err(ParserError::Generic( @@ -1050,7 +1278,7 @@ impl<'a> SceneParser<'a> { } } - self.target.end_of_files(); + self.target.end_of_files()?; Ok(()) } @@ -1079,7 +1307,12 @@ impl<'a> SceneParser<'a> { fn parse_basic_entry(&mut self, mut func: F) -> Result<(), ParserError> where - F: FnMut(&mut dyn ParserTarget, &str, &ParsedParameterVector, FileLoc), + F: FnMut( + &mut dyn ParserTarget, + &str, + &ParsedParameterVector, + FileLoc, + ) -> Result<(), ParserError>, { let type_token = self.next_token_required()?; let type_name = if type_token.is_quoted() { @@ -1089,7 +1322,6 @@ impl<'a> SceneParser<'a> { }; let params = self.parse_parameters()?; - func(self.target, &type_name, ¶ms, type_token.loc); - Ok(()) + func(self.target, &type_name, ¶ms, type_token.loc) } }