Cleanup of shared codebase to no_std and spirv compatibility

This commit is contained in:
Wito Wiala 2026-02-20 16:39:27 +00:00
parent b36105edc1
commit 7ebed27d4a
100 changed files with 1648 additions and 1104 deletions

1
.gitignore vendored
View file

@ -5,6 +5,7 @@ target/
flip.rs flip.rs
.vscode .vscode
rust-analyzer.json rust-analyzer.json
rust-analyzer.toml
data/ data/
src/gpu/ src/gpu/
src/tests/ src/tests/

View file

@ -6,11 +6,12 @@ edition = "2024"
[features] [features]
default = [] default = []
use_f64 = [] use_f64 = []
use_gpu = [] use_gpu = ["dep:wgpu"]
use_nvtx = [] use_nvtx = ["dep:nvtx"]
cuda = ["dep:cudarc", "dep:cust", "dep:cust_raw"] cuda = ["dep:cudarc", "dep:cust", "dep:cust_raw", "dep:cuda-runtime-sys"]
vulkan = ["ash", "gpu-allocator"] vulkan = ["ash", "gpu-allocator"]
ash = ["dep:ash"] ash = ["dep:ash"]
gpu-allocator = ["dep:gpu-allocator"]
[dependencies] [dependencies]
anyhow = "1.0.100" anyhow = "1.0.100"
@ -22,7 +23,6 @@ indicatif = "0.18.3"
lazy_static = "1.5.0" lazy_static = "1.5.0"
log = "0.4.29" log = "0.4.29"
memmap2 = "0.9.9" memmap2 = "0.9.9"
nvtx = "1.3.0"
parking_lot = "0.12.5" parking_lot = "0.12.5"
paste = "1.0.15" paste = "1.0.15"
qoi = "0.4.1" qoi = "0.4.1"
@ -30,16 +30,6 @@ rand = "0.9.2"
rayon = "1.11.0" rayon = "1.11.0"
thiserror = "2.0.17" thiserror = "2.0.17"
unicode-normalization = "0.1.25" 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" ptex = "0.3.0"
slice = "0.0.4" slice = "0.0.4"
crossbeam-channel = "0.5.15" crossbeam-channel = "0.5.15"
@ -49,7 +39,18 @@ enum_dispatch = "0.3.13"
bytemuck = "1.24.0" bytemuck = "1.24.0"
once_cell = "1.21.3" once_cell = "1.21.3"
smallvec = "1.15.1" 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 } cudarc = { version = "0.18.2", features = ["cuda-13000"], optional = true }
[build-dependencies] [build-dependencies]
@ -59,7 +60,7 @@ cc = "1.2.53"
[workspace] [workspace]
members = ["shared"] members = ["shared"]
exclude = ["crates/ptex-filter"] exclude = ["crates/ptex-filter", "kernels"]
[lints.clippy] [lints.clippy]
excessive_precision = "allow" excessive_precision = "allow"

View file

@ -1,11 +1,18 @@
[package] [package]
name = "kernels" name = "kernels"
version = "0.1.0" version = "0.1.0"
edition = "2024" edition = "2021"
[dependencies]
cuda_std = { git = "https://github.com/rust-gpu/rust-cuda", rev = "7fa76f3d717038a92c90bf4a482b0b8dd3259344" }
shared = { path = "../shared", features = ["cuda"] }
[lib] [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"

View file

@ -1,30 +1,20 @@
#![cfg_attr(target_arch = "nvptx64", no_std)] #![cfg_attr(target_arch = "spirv", no_std)]
#![cfg_attr(target_arch = "nvptx64", feature(abi_ptx))]
use cuda_std::prelude::*; use spirv_std::spirv;
/// Scales each element: data[i] *= scale /// Core logic — testable on CPU
#[kernel] pub fn scale_kernel_logic(idx: usize, input: &[f32], output: &mut [f32], scale: f32) {
#[allow(improper_ctypes_definitions)] if idx < input.len() {
pub unsafe fn scale_array(data: *mut f32, len: u32, scale: f32) { output[idx] = input[idx] * scale;
let idx = thread::index_1d() as u32;
if idx >= len {
return;
} }
let ptr = unsafe { data.add(idx as usize) };
*ptr = *ptr * scale;
} }
/// Adds two arrays: c[i] = a[i] + b[i] #[spirv(compute(threads(64)))]
#[kernel] pub fn scale_kernel(
#[allow(improper_ctypes_definitions)] #[spirv(global_invocation_id)] id: spirv_std::glam::UVec3,
pub unsafe fn add_arrays(a: *const f32, b: *const f32, c: *mut f32, len: u32) { #[spirv(storage_buffer, descriptor_set = 0, binding = 0)] input: &[f32],
let idx = thread::index_1d() as u32; #[spirv(storage_buffer, descriptor_set = 0, binding = 1)] output: &mut [f32],
if idx >= len { #[spirv(push_constant)] scale: &f32,
return; ) {
} scale_kernel_logic(id.x as usize, input, output, *scale);
let i = idx as usize;
*c.add(i) = *a.add(i) + *b.add(i);
} }

View file

@ -5,20 +5,12 @@ edition = "2024"
[dependencies] [dependencies]
bitflags = "2.10.0" bitflags = "2.10.0"
bumpalo = "3.19.1"
bytemuck = { version = "1.24.0", features = ["derive"] } bytemuck = { version = "1.24.0", features = ["derive"] }
enum_dispatch = "0.3.13" enum_dispatch = "0.3.13"
log = "0.4.29" num-traits = { version = "0.2.19", default-features = false, features = ["libm"] }
num = "0.4.3"
num-integer = "0.1.46"
num-traits = "0.2.19"
once_cell = "1.21.3"
smallvec = "1.15.1"
cuda_std = { git = "https://github.com/Rust-GPU/Rust-CUDA", branch = "main", default-features = false, optional = true } 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] [features]
use_f64 = [] use_f64 = []
cuda = ["cuda_std"] cuda = ["cuda_std"]
cpu_debug = []

View file

@ -18,6 +18,7 @@ use crate::utils::sampling::{
}; };
use crate::{Float, INV_2_PI, INV_PI, PI}; use crate::{Float, INV_2_PI, INV_PI, PI};
use core::any::Any; use core::any::Any;
use num_traits::Float as NumFloat;
static P_MAX: usize = 3; static P_MAX: usize = 3;
#[repr(C)] #[repr(C)]
@ -81,7 +82,7 @@ impl HairBxDF {
let ap0 = SampledSpectrum::new(f); let ap0 = SampledSpectrum::new(f);
let ap1 = t * (1.0 - f).powi(2); let ap1 = t * (1.0 - f).powi(2);
let tf = t * f; let tf = t * f;
std::array::from_fn(|p| match p { core::array::from_fn(|p| match p {
0 => ap0, 0 => ap0,
1 => ap1, 1 => ap1,
_ if p < P_MAX => ap1 * tf.pow_int(p - 1), _ if p < P_MAX => ap1 * tf.pow_int(p - 1),
@ -133,7 +134,7 @@ impl HairBxDF {
let t = t_value.exp(); let t = t_value.exp();
let ap = Self::ap(cos_theta_o, self.eta, self.h, t); let ap = Self::ap(cos_theta_o, self.eta, self.h, t);
let sum_y: Float = ap.iter().map(|s| s.average()).sum(); 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( pub fn sigma_a_from_concentration(

View file

@ -8,6 +8,7 @@ use crate::spectra::SampledSpectrum;
use crate::utils::sampling::{cosine_hemisphere_pdf, sample_cosine_hemisphere}; use crate::utils::sampling::{cosine_hemisphere_pdf, sample_cosine_hemisphere};
use crate::{Float, INV_PI}; use crate::{Float, INV_PI};
use core::any::Any; use core::any::Any;
use num_traits::Float as NumFloat;
#[repr(C)] #[repr(C)]
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]

View file

@ -11,6 +11,7 @@ use crate::utils::math::square;
use crate::utils::sampling::{cosine_hemisphere_pdf, sample_cosine_hemisphere}; use crate::utils::sampling::{cosine_hemisphere_pdf, sample_cosine_hemisphere};
use crate::{Float, INV_PI}; use crate::{Float, INV_PI};
use core::any::Any; use core::any::Any;
use num_traits::Float as NumFloat;
#[repr(C)] #[repr(C)]
#[derive(Debug, Copy, Clone)] #[derive(Debug, Copy, Clone)]

View file

@ -9,7 +9,6 @@ use crate::core::geometry::{
spherical_direction, spherical_theta, spherical_direction, spherical_theta,
}; };
use crate::core::medium::{HGPhaseFunction, PhaseFunctionTrait}; use crate::core::medium::{HGPhaseFunction, PhaseFunctionTrait};
use crate::core::options::get_options;
use crate::core::scattering::{ use crate::core::scattering::{
TrowbridgeReitzDistribution, fr_complex, fr_complex_from_spectrum, fr_dielectric, reflect, TrowbridgeReitzDistribution, fr_complex, fr_complex_from_spectrum, fr_dielectric, reflect,
refract, 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 crate::{Float, INV_2_PI, INV_4_PI, INV_PI, ONE_MINUS_EPSILON, PI, PI_OVER_2};
use core::any::Any; use core::any::Any;
use num_traits::Float as NumFloat;
#[derive(Copy, Clone)] #[derive(Copy, Clone)]
pub enum TopOrBottom<'a, T, B> { pub enum TopOrBottom<'a, T, B> {
@ -91,6 +91,7 @@ where
albedo: SampledSpectrum, albedo: SampledSpectrum,
max_depth: u32, max_depth: u32,
n_samples: u32, n_samples: u32,
seed: i32,
} }
impl<T, B, const TWO_SIDED: bool> LayeredBxDF<T, B, TWO_SIDED> impl<T, B, const TWO_SIDED: bool> LayeredBxDF<T, B, TWO_SIDED>
@ -106,6 +107,7 @@ where
g: Float, g: Float,
max_depth: u32, max_depth: u32,
n_samples: u32, n_samples: u32,
seed: i32,
) -> Self { ) -> Self {
Self { Self {
top, top,
@ -115,6 +117,7 @@ where
albedo, albedo,
max_depth, max_depth,
n_samples, n_samples,
seed,
} }
} }
@ -369,7 +372,7 @@ where
f = self.n_samples as Float * enter_interface.f(wo, wi, mode); 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 hash1 = hash_buffer(&[wi.x(), wi.y(), wi.z()], 0);
let mut rng = Rng::new_with_offset(hash0, hash1); let mut rng = Rng::new_with_offset(hash0, hash1);
@ -415,7 +418,7 @@ where
let mut specular_path = bs.is_specular(); let mut specular_path = bs.is_specular();
// Declare RNG for layered BSDF sampling // 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 hash1 = hash_buffer(&[uc, u.x(), u.y()], 0);
let mut rng = Rng::new_with_offset(hash0, hash1); let mut rng = Rng::new_with_offset(hash0, hash1);
@ -517,7 +520,7 @@ where
wi = -wi; 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 hash1 = hash_buffer(&[wo.x(), wo.y(), wo.z()], 0);
let mut rng = Rng::new_with_offset(hash0, hash1); let mut rng = Rng::new_with_offset(hash0, hash1);

View file

@ -11,6 +11,7 @@ use crate::utils::ptr::Ptr;
use crate::utils::sampling::{PiecewiseLinear2D, cosine_hemisphere_pdf, sample_cosine_hemisphere}; use crate::utils::sampling::{PiecewiseLinear2D, cosine_hemisphere_pdf, sample_cosine_hemisphere};
use crate::{Float, INV_PI, PI, PI_OVER_2}; use crate::{Float, INV_PI, PI, PI_OVER_2};
use core::any::Any; use core::any::Any;
use num_traits::Float as NumFloat;
#[repr(C)] #[repr(C)]
#[derive(Debug, Copy, Clone)] #[derive(Debug, Copy, Clone)]

View file

@ -13,6 +13,7 @@ use crate::core::scattering::refract;
use crate::spectra::{SampledSpectrum, SampledWavelengths}; use crate::spectra::{SampledSpectrum, SampledWavelengths};
use crate::utils::Ptr; use crate::utils::Ptr;
use crate::utils::math::{lerp, quadratic, square}; use crate::utils::math::{lerp, quadratic, square};
use num_traits::Float as NumFloat;
#[repr(C)] #[repr(C)]
#[derive(Debug, Copy, Clone)] #[derive(Debug, Copy, Clone)]
@ -35,87 +36,17 @@ const EXIT_PUPIL_SAMPLES: usize = 64;
#[repr(C)] #[repr(C)]
#[derive(Debug, Copy, Clone)] #[derive(Debug, Copy, Clone)]
pub struct RealisticCamera { pub struct RealisticCamera {
base: CameraBase, pub base: CameraBase,
focus_distance: Float, pub focus_distance: Float,
set_aperture_diameter: Float, pub set_aperture_diameter: Float,
aperture_image: Ptr<DeviceImage>, pub aperture_image: Ptr<DeviceImage>,
element_interfaces: Ptr<LensElementInterface>, pub element_interfaces: Ptr<LensElementInterface>,
n_elements: usize, pub n_elements: usize,
physical_extent: Bounds2f, pub physical_extent: Bounds2f,
exit_pupil_bounds: [Bounds2f; EXIT_PUPIL_SAMPLES], pub exit_pupil_bounds: [Bounds2f; EXIT_PUPIL_SAMPLES],
} }
#[cfg(not(target_os = "cuda"))]
impl RealisticCamera { impl RealisticCamera {
pub fn new(
base: CameraBase,
lens_params: &[Float],
focus_distance: Float,
set_aperture_diameter: Float,
aperture_image: Ptr<DeviceImage>,
) -> 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<LensElementInterface> = 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) { pub fn compute_cardinal_points(r_in: Ray, r_out: Ray) -> (Float, Float) {
let tf = -r_out.o.x() / r_out.d.x(); let tf = -r_out.o.x() / r_out.d.x();
let tp = (r_in.o.x() - 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() { if pupil_bounds.is_degenerate() {
print!(
"Unable to find exit pupil in x = {},{} on film.",
film_x_0, film_x_1
);
return pupil_bounds; return pupil_bounds;
} }

View file

@ -6,6 +6,7 @@ use crate::core::pbrt::{Float, PI};
use crate::core::sampler::CameraSample; use crate::core::sampler::CameraSample;
use crate::spectra::{SampledSpectrum, SampledWavelengths}; use crate::spectra::{SampledSpectrum, SampledWavelengths};
use crate::utils::math::{equal_area_square_to_sphere, wrap_equal_area_square}; use crate::utils::math::{equal_area_square_to_sphere, wrap_equal_area_square};
use num_traits::Float as NumFloat;
#[repr(C)] #[repr(C)]
#[derive(Debug, Copy, Clone, PartialEq)] #[derive(Debug, Copy, Clone, PartialEq)]

View file

@ -9,7 +9,7 @@ use crate::utils::math::{catmull_rom_weights, square};
use crate::utils::sampling::sample_catmull_rom_2d; use crate::utils::sampling::sample_catmull_rom_2d;
use crate::{Float, PI}; use crate::{Float, PI};
use enum_dispatch::enum_dispatch; use enum_dispatch::enum_dispatch;
use std::sync::Arc; use num_traits::Float as NumFloat;
#[derive(Debug)] #[derive(Debug)]
pub struct BSSRDFSample { pub struct BSSRDFSample {

View file

@ -5,15 +5,15 @@ use crate::core::geometry::{
}; };
use crate::core::interaction::Interaction; use crate::core::interaction::Interaction;
use crate::core::medium::Medium; use crate::core::medium::Medium;
use crate::core::options::RenderingCoordinateSystem;
use crate::core::pbrt::Float; use crate::core::pbrt::Float;
use crate::core::sampler::CameraSample; use crate::core::sampler::CameraSample;
use crate::spectra::{SampledSpectrum, SampledWavelengths}; use crate::spectra::{SampledSpectrum, SampledWavelengths};
use crate::utils::math::lerp; use crate::utils::math::lerp;
use crate::utils::options::RenderingCoordinateSystem;
use crate::utils::ptr::Ptr; use crate::utils::ptr::Ptr;
use crate::utils::transform::{AnimatedTransform, Transform}; use crate::utils::transform::{AnimatedTransform, Transform};
use enum_dispatch::enum_dispatch; use enum_dispatch::enum_dispatch;
use num_traits::Float as NumFloat;
#[repr(C)] #[repr(C)]
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]

View file

@ -1,6 +1,6 @@
use std::any::TypeId; use core::any::TypeId;
use std::fmt; use core::fmt;
use std::ops::{ use core::ops::{
Add, AddAssign, Div, DivAssign, Index, IndexMut, Mul, MulAssign, Neg, Sub, SubAssign, 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::Ptr;
use crate::utils::find_interval; use crate::utils::find_interval;
use crate::utils::math::{SquareMatrix, SquareMatrix3f, clamp, evaluate_polynomial, lerp}; use crate::utils::math::{SquareMatrix, SquareMatrix3f, clamp, evaluate_polynomial, lerp};
use num_traits::Float as NumFloat;
use enum_dispatch::enum_dispatch; use enum_dispatch::enum_dispatch;
@ -35,7 +36,7 @@ impl From<[Float; 3]> for XYZ {
impl<'a> IntoIterator for &'a XYZ { impl<'a> IntoIterator for &'a XYZ {
type Item = &'a Float; 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 { fn into_iter(self) -> Self::IntoIter {
[&self.x, &self.y, &self.z].into_iter() [&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 { impl<'a> IntoIterator for &'a RGB {
type Item = &'a Float; 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 { fn into_iter(self) -> Self::IntoIter {
[&self.r, &self.g, &self.b].into_iter() [&self.r, &self.g, &self.b].into_iter()

View file

@ -19,6 +19,7 @@ use crate::utils::math::{SquareMatrix, wrap_equal_area_square};
use crate::utils::sampling::VarianceEstimator; use crate::utils::sampling::VarianceEstimator;
use crate::utils::transform::AnimatedTransform; use crate::utils::transform::AnimatedTransform;
use crate::utils::{AtomicFloat, Ptr}; use crate::utils::{AtomicFloat, Ptr};
use num_traits::Float as NumFloat;
#[repr(C)] #[repr(C)]
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
@ -298,9 +299,9 @@ pub struct SpectralPixel {
impl Clone for SpectralPixel { impl Clone for SpectralPixel {
fn clone(&self) -> Self { fn clone(&self) -> 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_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, bucket_offset: self.bucket_offset,
} }
} }
@ -309,9 +310,9 @@ impl Clone for SpectralPixel {
impl Default for SpectralPixel { impl Default for SpectralPixel {
fn default() -> Self { fn default() -> Self {
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_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, bucket_offset: 0,
} }
} }

View file

@ -1,12 +1,12 @@
use super::{Float, NumFloat}; use super::{Float, NumFloat};
use super::{Point, Point2f, Point3, Point3f, Vector, Vector2, Vector2f, Vector3, Vector3f}; 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::core::geometry::{max, min};
use crate::utils::interval::Interval; use crate::utils::interval::Interval;
use crate::utils::math::lerp; use crate::utils::math::lerp;
use core::mem;
use core::ops::{Add, Div, DivAssign, Mul, Sub};
use num_traits::{Bounded, Num}; use num_traits::{Bounded, Num};
use std::mem;
use std::ops::{Add, Div, DivAssign, Mul, Sub};
// AABB BOUNDING BOXES // AABB BOUNDING BOXES
@ -18,7 +18,7 @@ pub struct Bounds<T, const N: usize> {
impl<'a, T, const N: usize> IntoIterator for &'a Bounds<T, N> { impl<'a, T, const N: usize> IntoIterator for &'a Bounds<T, N> {
type Item = &'a Point<T, N>; type Item = &'a Point<T, N>;
type IntoIter = std::array::IntoIter<&'a Point<T, N>, 2>; type IntoIter = core::array::IntoIter<&'a Point<T, N>, 2>;
fn into_iter(self) -> Self::IntoIter { fn into_iter(self) -> Self::IntoIter {
[&self.p_min, &self.p_max].into_iter() [&self.p_min, &self.p_max].into_iter()
@ -137,7 +137,7 @@ where
} }
pub fn corner(&self, corner_index: usize) -> Point<T, N> { pub fn corner(&self, corner_index: usize) -> Point<T, N> {
Point(std::array::from_fn(|i| { Point(core::array::from_fn(|i| {
if (corner_index >> i) & 1 == 1 { if (corner_index >> i) & 1 == 1 {
self.p_max[i] self.p_max[i]
} else { } else {
@ -206,7 +206,7 @@ where
impl<T> Bounds3<T> impl<T> Bounds3<T>
where where
T: NumFloat + PartialOrd + Copy + Default + Sqrt, T: NumFloat + PartialOrd + Copy + Default + SqrtExt,
{ {
pub fn bounding_sphere(&self) -> (Point3<T>, T) { pub fn bounding_sphere(&self) -> (Point3<T>, T) {
let two = T::one() + T::one(); let two = T::one() + T::one();

View file

@ -1,6 +1,7 @@
use super::{Bounds3f, Float, PI, Point3f, Vector3f, VectorLike}; use super::{Bounds3f, Float, PI, Point3f, Vector3f, VectorLike};
use crate::utils::math::{degrees, safe_acos, safe_asin, safe_sqrt, square}; use crate::utils::math::{degrees, safe_acos, safe_asin, safe_sqrt, square};
use crate::utils::transform::TransformGeneric; use crate::utils::transform::TransformGeneric;
use num_traits::Float as NumFloat;
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct DirectionCone { pub struct DirectionCone {

View file

@ -12,7 +12,7 @@ pub use self::primitives::{
Vector3i, Vector3i,
}; };
pub use self::ray::{Ray, RayDifferential}; 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::core::pbrt::{Float, PI};
use crate::utils::math::{clamp, square}; use crate::utils::math::{clamp, square};

View file

@ -1,13 +1,13 @@
use super::traits::{Sqrt, Tuple, VectorLike}; use super::traits::{SqrtExt, Tuple, VectorLike};
use super::{Float, NumFloat, PI}; use super::{Float, NumFloat, PI};
use crate::utils::interval::Interval; use crate::utils::interval::Interval;
use crate::utils::math::{clamp, difference_of_products, quadratic, safe_asin}; use crate::utils::math::{clamp, difference_of_products, quadratic, safe_asin};
use num_traits::{AsPrimitive, FloatConst, Num, Signed, Zero}; use core::hash::{Hash, Hasher};
use std::hash::{Hash, Hasher}; use core::iter::Sum;
use std::iter::Sum; use core::ops::{
use std::ops::{
Add, AddAssign, Div, DivAssign, Index, IndexMut, Mul, MulAssign, Neg, Sub, SubAssign, Add, AddAssign, Div, DivAssign, Index, IndexMut, Mul, MulAssign, Neg, Sub, SubAssign,
}; };
use num_traits::{AsPrimitive, FloatConst, Num, Signed, Zero};
pub trait MulAdd<M = Self, A = Self> { pub trait MulAdd<M = Self, A = Self> {
type Output; type Output;
@ -18,7 +18,7 @@ impl MulAdd<Float, Float> for Float {
type Output = Float; type Output = Float;
#[inline(always)] #[inline(always)]
fn mul_add(self, multiplier: Float, addend: Float) -> Self::Output { 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<Output = T> + Mul<Output = T>
+ Sub<Output = T> + Sub<Output = T>
+ Div<Output = T> + Div<Output = T>
+ Sqrt, + SqrtExt,
{ {
type Scalar = T; type Scalar = T;
fn dot(self, rhs: Self) -> T { fn dot(self, rhs: Self) -> T {
@ -449,7 +449,7 @@ impl<T: Copy, const N: usize> From<Point<T, N>> for Vector<T, N> {
impl<T, const N: usize> Point<T, N> impl<T, const N: usize> Point<T, N>
where where
T: NumFloat + Sqrt, T: NumFloat + SqrtExt,
{ {
pub fn distance(self, other: Self) -> T { pub fn distance(self, other: Self) -> T {
(self - other).norm() (self - other).norm()
@ -814,7 +814,7 @@ impl<const N: usize> From<Point<i32, N>> for Point<Float, N> {
impl<T> Normal3<T> impl<T> Normal3<T>
where where
T: Num + PartialOrd + Copy + Neg<Output = T> + Sqrt, T: Num + PartialOrd + Copy + Neg<Output = T> + SqrtExt,
{ {
pub fn face_forward(self, v: impl Into<Vector3<T>>) -> Self { pub fn face_forward(self, v: impl Into<Vector3<T>>) -> Self {
let v: Vector3<T> = v.into(); let v: Vector3<T> = v.into();

View file

@ -1,8 +1,8 @@
use crate::core::pbrt::Float; use crate::core::pbrt::Float;
use crate::utils::interval::Interval; use crate::utils::interval::Interval;
use crate::utils::math::{next_float_down, next_float_up}; 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 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<T, const N: usize>: pub trait Tuple<T, const N: usize>:
Sized + Copy + Index<usize, Output = T> + IndexMut<usize> Sized + Copy + Index<usize, Output = T> + IndexMut<usize>
@ -77,7 +77,7 @@ pub trait VectorLike:
+ Div<Self::Scalar, Output = Self> + Div<Self::Scalar, Output = Self>
+ Mul<Self::Scalar, Output = Self> + Mul<Self::Scalar, Output = Self>
{ {
type Scalar: Copy + Zero + Add<Output = Self::Scalar> + Mul<Output = Self::Scalar> + Sqrt; type Scalar: Copy + Zero + Add<Output = Self::Scalar> + Mul<Output = Self::Scalar> + SqrtExt;
fn dot(self, rhs: Self) -> Self::Scalar; fn dot(self, rhs: Self) -> Self::Scalar;
fn norm_squared(self) -> Self::Scalar { fn norm_squared(self) -> Self::Scalar {
@ -96,7 +96,7 @@ pub trait VectorLike:
} }
fn norm(&self) -> Self::Scalar { fn norm(&self) -> Self::Scalar {
self.norm_squared().sqrt() self.norm_squared().sqrt_ext()
} }
fn normalize(self) -> Self fn normalize(self) -> Self
@ -119,36 +119,36 @@ pub trait VectorLike:
} }
} }
pub trait Sqrt { pub trait SqrtExt {
fn sqrt(self) -> Self; fn sqrt_ext(self) -> Self;
} }
impl Sqrt for Float { impl SqrtExt for Float {
fn sqrt(self) -> Self { fn sqrt_ext(self) -> Self {
self.sqrt() <Self as num_traits::Float>::sqrt(self)
} }
} }
impl Sqrt for f64 { impl SqrtExt for f64 {
fn sqrt(self) -> Self { fn sqrt_ext(self) -> Self {
self.sqrt() <Self as num_traits::Float>::sqrt(self)
} }
} }
impl Sqrt for i32 { impl SqrtExt for i32 {
fn sqrt(self) -> Self { fn sqrt_ext(self) -> Self {
self.isqrt() self.isqrt()
} }
} }
impl Sqrt for u32 { impl SqrtExt for u32 {
fn sqrt(self) -> Self { fn sqrt_ext(self) -> Self {
self.isqrt() self.isqrt()
} }
} }
impl Sqrt for Interval { impl SqrtExt for Interval {
fn sqrt(self) -> Self { fn sqrt_ext(self) -> Self {
let low = if self.low < 0.0 { let low = if self.low < 0.0 {
0.0 0.0
} else { } else {

View file

@ -3,11 +3,10 @@ use crate::core::color::{ColorEncoding, ColorEncodingTrait, LINEAR};
use crate::core::geometry::{Bounds2f, Point2f, Point2fi, Point2i}; use crate::core::geometry::{Bounds2f, Point2f, Point2fi, Point2i};
use crate::utils::Ptr; use crate::utils::Ptr;
use crate::utils::containers::DeviceArray2D; 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 core::hash;
use half::f16; use core::ops::{Deref, DerefMut};
use smallvec::{SmallVec, smallvec}; use num_traits::Float as NumFloat;
use std::ops::{Deref, DerefMut};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum WrapMode { pub enum WrapMode {
@ -36,8 +35,8 @@ pub enum PixelFormat {
F32, F32,
} }
impl std::fmt::Display for PixelFormat { impl core::fmt::Display for PixelFormat {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self { match self {
PixelFormat::U8 => write!(f, "U256"), PixelFormat::U8 => write!(f, "U256"),
PixelFormat::F16 => write!(f, "Half"), PixelFormat::F16 => write!(f, "Half"),
@ -72,7 +71,7 @@ impl PixelFormat {
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug)]
pub enum Pixels { pub enum Pixels {
U8(Ptr<u8>), U8(Ptr<u8>),
F16(Ptr<f16>), F16(Ptr<u16>),
F32(Ptr<f32>), F32(Ptr<f32>),
} }
@ -180,7 +179,7 @@ impl ImageAccess for DeviceImage {
} }
Pixels::F16(ptr) => { Pixels::F16(ptr) => {
let raw_val = *ptr.add(offset as usize); 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), Pixels::F32(ptr) => *ptr.add(offset as usize),
} }

View file

@ -13,7 +13,6 @@ use crate::core::material::{
Material, MaterialEvalContext, MaterialTrait, NormalBumpEvalContext, bump_map, normal_map, Material, MaterialEvalContext, MaterialTrait, NormalBumpEvalContext, bump_map, normal_map,
}; };
use crate::core::medium::{Medium, MediumInterface, PhaseFunction}; use crate::core::medium::{Medium, MediumInterface, PhaseFunction};
use crate::core::options::get_options;
use crate::core::sampler::{Sampler, SamplerTrait}; use crate::core::sampler::{Sampler, SamplerTrait};
use crate::core::shape::Shape; use crate::core::shape::Shape;
use crate::core::texture::{GPUFloatTexture, UniversalTextureEvaluator}; use crate::core::texture::{GPUFloatTexture, UniversalTextureEvaluator};
@ -21,8 +20,6 @@ use crate::spectra::{SampledSpectrum, SampledWavelengths};
use crate::utils::Ptr; use crate::utils::Ptr;
use crate::utils::math::{clamp, difference_of_products, square}; use crate::utils::math::{clamp, difference_of_products, square};
use enum_dispatch::enum_dispatch; use enum_dispatch::enum_dispatch;
use std::any::Any;
use std::default;
#[repr(C)] #[repr(C)]
#[derive(Default, Copy, Clone, Debug)] #[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<BSDF> {
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<BSSRDF> {
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( fn compute_bump_geom(
&mut self, &mut self,
tex_eval: &UniversalTextureEvaluator, tex_eval: &UniversalTextureEvaluator,

View file

@ -1,6 +1,6 @@
use crate::materials::*; use crate::materials::*;
use core::ops::Deref;
use enum_dispatch::enum_dispatch; use enum_dispatch::enum_dispatch;
use std::ops::Deref;
use crate::Float; use crate::Float;
use crate::bxdfs::{ use crate::bxdfs::{

View file

@ -1,5 +1,4 @@
use enum_dispatch::enum_dispatch; use enum_dispatch::enum_dispatch;
use std::sync::Arc;
use crate::core::geometry::{ use crate::core::geometry::{
Bounds3f, Frame, Point2f, Point3f, Point3i, Ray, Vector3f, VectorLike, spherical_direction, 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::ptr::Ptr;
use crate::utils::rng::Rng; use crate::utils::rng::Rng;
use crate::utils::transform::Transform; use crate::utils::transform::Transform;
use num_traits::Float as NumFloat;
#[repr(C)] #[repr(C)]
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
@ -371,7 +371,7 @@ impl MediumProperties {
} }
#[enum_dispatch] #[enum_dispatch]
pub trait MediumTrait: Send + Sync + std::fmt::Debug { pub trait MediumTrait: Send + Sync + core::fmt::Debug {
fn is_emissive(&self) -> bool; fn is_emissive(&self) -> bool;
fn sample_point(&self, p: Point3f, lambda: &SampledWavelengths) -> MediumProperties; fn sample_point(&self, p: Point3f, lambda: &SampledWavelengths) -> MediumProperties;
fn sample_ray( fn sample_ray(

View file

@ -11,7 +11,6 @@ pub mod interaction;
pub mod light; pub mod light;
pub mod material; pub mod material;
pub mod medium; pub mod medium;
pub mod options;
pub mod pbrt; pub mod pbrt;
pub mod primitive; pub mod primitive;
pub mod sampler; pub mod sampler;

View file

@ -1,9 +1,6 @@
use crate::core::geometry::Lerp; use crate::core::geometry::Lerp;
use core::sync::atomic::{AtomicU64, Ordering as SyncOrdering}; use core::ops::{Add, Mul};
use num_traits::{Num, PrimInt}; 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::image::DeviceImage;
use crate::core::light::LightTrait; use crate::core::light::LightTrait;
@ -15,40 +12,6 @@ use crate::utils::Ptr;
pub type Float = f32; 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<DenselySampledSpectrum>;
// type ColorSpaceRef = Ptr<RGBColorSpace>;
// 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<Shape>;
// type TextureRef = Ptr<GPUFloatTexture>;
// type ColorSpaceRef = Ptr<RGBColorSpace>;
// type DenseSpectrumRef = Ptr<DenselySampledSpectrum>;
// type ImageRef = Ptr<DeviceImage>;
// type DiffuseLight = Ptr<DiffuseAreaLight<Device>>;
// type PointLight = Ptr<PointLight<Device>>;
// type UniformInfiniteLight = Ptr<UniformInfiniteLight<Device>>;
// type PortalInfiniteLight = Ptr<PortalInfiniteLight<Device>>;
// type ImageInfiniteLight = Ptr<ImageInfiniteLight<Device>>;
// type SpotLight = Ptr<SpotLight<Device>>;
// }
//
#[cfg(not(feature = "use_f64"))] #[cfg(not(feature = "use_f64"))]
pub type FloatBits = u32; pub type FloatBits = u32;
@ -134,45 +97,50 @@ impl FloatBitOps for f64 {
pub const MACHINE_EPSILON: Float = Float::EPSILON * 0.5; pub const MACHINE_EPSILON: Float = Float::EPSILON * 0.5;
pub const SHADOW_EPSILON: Float = 0.0001; pub const SHADOW_EPSILON: Float = 0.0001;
pub const ONE_MINUS_EPSILON: Float = 0.99999994; pub const ONE_MINUS_EPSILON: Float = 0.99999994;
pub const PI: Float = std::f32::consts::PI; pub const PI: Float = core::f32::consts::PI;
pub const INV_PI: Float = 0.318_309_886_183_790_671_54; 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_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 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_2: Float = core::f32::consts::FRAC_PI_2;
pub const PI_OVER_4: Float = 0.785_398_163_397_448_309_61; pub const PI_OVER_4: Float = core::f32::consts::FRAC_PI_4;
pub const SQRT_2: Float = 1.414_213_562_373_095_048_80; pub const SQRT_2: Float = core::f32::consts::SQRT_2;
#[inline] #[inline]
pub fn gamma(n: i32) -> Float { pub fn gamma(n: i32) -> Float {
n as Float * MACHINE_EPSILON / (1. - n as Float * MACHINE_EPSILON) n as Float * MACHINE_EPSILON / (1. - n as Float * MACHINE_EPSILON)
} }
pub static RARE_EVENT_TOTAL_CALLS: AtomicU64 = AtomicU64::new(0); #[cfg(feature = "cpu_debug")]
pub static RARE_EVENT_CONDITION_MET: AtomicU64 = AtomicU64::new(0); 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_export]
macro_rules! check_rare { macro_rules! check_rare {
($frequency_threshold:expr, $condition:expr) => { ($frequency_threshold:expr, $condition:expr) => {
use core::sync::atomic::{AtomicU64, Ordering as SyncOrdering}; use core::sync::atomic::{AtomicU64, Ordering as SyncOrdering};
const CHECK_INTERVAL: u64 = 4096; 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 { if $condition {
RARE_EVENT_CONDITION_MET.fetch_add(1, SyncOrdering::Relaxed); RARE_EVENT_CONDITION_MET.fetch_add(1, SyncOrdering::Relaxed);
} }
if (total_calls + 1) % CHECK_INTERVAL == 0 { if (total_calls + 1) % CHECK_INTERVAL == 0 {
let met_count = RARE_EVENT_CONDITION_MET.load(SyncOrdering::Relaxed); let met_count = RARE_EVENT_CONDITION_MET.load(SyncOrdering::Relaxed);
if met_count > 0 { if met_count > 0 {
let frequency = met_count as f64 / (total_calls + 1) as f64; let frequency = met_count as f64 / (total_calls + 1) as f64;
if frequency > $frequency_threshold { if frequency > $frequency_threshold {
panic!( panic!(
"Rare event occurred with frequency {} which is > threshold {}", "Rare event occurred with frequency {} which is > threshold {}",
frequency, $frequency_threshold frequency, $frequency_threshold
); );
}
} }
} }
} };
}; }
} }

View file

@ -11,7 +11,6 @@ use crate::utils::hash::hash_float;
use crate::utils::transform::{AnimatedTransform, Transform}; use crate::utils::transform::{AnimatedTransform, Transform};
use enum_dispatch::enum_dispatch; use enum_dispatch::enum_dispatch;
use std::sync::Arc;
#[enum_dispatch] #[enum_dispatch]
pub trait PrimitiveTrait: Send + Sync { pub trait PrimitiveTrait: Send + Sync {

View file

@ -1,6 +1,5 @@
use crate::core::filter::FilterTrait; use crate::core::filter::FilterTrait;
use crate::core::geometry::{Bounds2f, Point2f, Point2i, Vector2f}; 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::core::pbrt::{Float, ONE_MINUS_EPSILON, PI, PI_OVER_2, PI_OVER_4};
use crate::utils::Ptr; use crate::utils::Ptr;
use crate::utils::containers::DeviceArray2D; use crate::utils::containers::DeviceArray2D;
@ -14,7 +13,6 @@ use crate::utils::rng::Rng;
use crate::utils::sobol::N_SOBOL_DIMENSIONS; use crate::utils::sobol::N_SOBOL_DIMENSIONS;
use crate::utils::{hash::*, sobol}; use crate::utils::{hash::*, sobol};
use enum_dispatch::enum_dispatch; use enum_dispatch::enum_dispatch;
use rand::seq::index::sample;
#[repr(C)] #[repr(C)]
#[derive(Debug, Default, Clone, Copy)] #[derive(Debug, Default, Clone, Copy)]

View file

@ -6,8 +6,9 @@ use crate::core::pbrt::{Float, PI};
use crate::spectra::{N_SPECTRUM_SAMPLES, SampledSpectrum}; use crate::spectra::{N_SPECTRUM_SAMPLES, SampledSpectrum};
use crate::utils::math::{clamp, lerp, safe_sqrt, square}; use crate::utils::math::{clamp, lerp, safe_sqrt, square};
use crate::utils::sampling::sample_uniform_disk_polar; 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)] #[repr(C)]
#[derive(Debug, Default, Clone, Copy)] #[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. (square(r_parl) + square(r_perp)) / 2.
} }
pub fn fr_complex(cos_theta_i: Float, eta: Complex<Float>) -> Float { pub fn fr_complex(cos_theta_i: Float, eta: Complex) -> Float {
let cos_corr = clamp(cos_theta_i, 0., 1.); let cos_corr = clamp(cos_theta_i, 0., 1.);
let sin2_theta_i = 1. - square(cos_corr); let sin2_theta_i = 1. - square(cos_corr);
let sin2_theta_t: Complex<Float> = sin2_theta_i / square(eta); let sin2_theta_t: Complex = sin2_theta_i / square(eta);
let cos2_theta_t: Complex<Float> = (1. - sin2_theta_t).sqrt(); 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_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); let r_perp = (cos_corr - eta * cos2_theta_t) / (cos_corr + eta * cos2_theta_t);

View file

@ -14,6 +14,7 @@ use crate::utils::Transform;
use crate::utils::math::square; use crate::utils::math::square;
use crate::{Float, INV_2_PI, INV_PI, PI}; use crate::{Float, INV_2_PI, INV_PI, PI};
use enum_dispatch::enum_dispatch; use enum_dispatch::enum_dispatch;
use num_traits::Float as NumFloat;
pub use crate::textures::*; pub use crate::textures::*;

View file

@ -1,6 +1,5 @@
use crate::Float; use crate::Float;
use bytemuck::cast_slice; use bytemuck::cast_slice;
use once_cell::sync::Lazy;
#[repr(C, align(16))] #[repr(C, align(16))]
struct AlignedData<const N: usize>(pub [u8; N]); struct AlignedData<const N: usize>(pub [u8; N]);

View file

@ -2,7 +2,6 @@ use crate::Float;
use crate::core::filter::{FilterSample, FilterSampler, FilterTrait}; use crate::core::filter::{FilterSample, FilterSampler, FilterTrait};
use crate::core::geometry::{Point2f, Vector2f}; use crate::core::geometry::{Point2f, Vector2f};
use crate::utils::math::{lerp, windowed_sinc}; use crate::utils::math::{lerp, windowed_sinc};
use rand::Rng;
#[repr(C)] #[repr(C)]
#[derive(Clone, Debug, Copy)] #[derive(Clone, Debug, Copy)]
@ -10,6 +9,7 @@ pub struct LanczosSincFilter {
pub radius: Vector2f, pub radius: Vector2f,
pub tau: Float, pub tau: Float,
pub sampler: FilterSampler, pub sampler: FilterSampler,
pub integral: Float,
} }
impl FilterTrait for LanczosSincFilter { impl FilterTrait for LanczosSincFilter {
@ -23,26 +23,7 @@ impl FilterTrait for LanczosSincFilter {
} }
fn integral(&self) -> Float { fn integral(&self) -> Float {
let sqrt_samples = 64; self.integral
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::<Float>()) / sqrt_samples as Float,
(y as Float + rng.random::<Float>()) / 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
} }
fn sample(&self, u: Point2f) -> FilterSample { fn sample(&self, u: Point2f) -> FilterSample {

View file

@ -1,6 +1,7 @@
use crate::Float; use crate::Float;
use crate::core::filter::{FilterSample, FilterSampler, FilterTrait}; use crate::core::filter::{FilterSample, FilterSampler, FilterTrait};
use crate::core::geometry::{Point2f, Vector2f}; use crate::core::geometry::{Point2f, Vector2f};
use num_traits::Float as NumFloat;
#[repr(C)] #[repr(C)]
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug)]

View file

@ -2,6 +2,7 @@ use crate::Float;
use crate::core::filter::{FilterSample, FilterTrait}; use crate::core::filter::{FilterSample, FilterTrait};
use crate::core::geometry::{Point2f, Vector2f}; use crate::core::geometry::{Point2f, Vector2f};
use crate::utils::math::sample_tent; use crate::utils::math::sample_tent;
use num_traits::Float as NumFloat;
#[repr(C)] #[repr(C)]
#[derive(Clone, Debug, Copy)] #[derive(Clone, Debug, Copy)]

View file

@ -1,7 +1,6 @@
#![allow(unused_imports, dead_code)] #![allow(unused_imports, dead_code)]
#![feature(float_erf)]
#![feature(f16)]
#![feature(associated_type_defaults)] #![feature(associated_type_defaults)]
#![no_std]
pub mod bxdfs; pub mod bxdfs;
pub mod cameras; pub mod cameras;
@ -16,4 +15,5 @@ pub mod textures;
pub mod utils; pub mod utils;
pub use core::pbrt::*; pub use core::pbrt::*;
pub use utils::PBRTOptions;
pub use utils::ptr::Ptr; pub use utils::ptr::Ptr;

View file

@ -17,6 +17,7 @@ use crate::spectra::*;
use crate::utils::hash::hash_float; use crate::utils::hash::hash_float;
use crate::utils::{Ptr, Transform}; use crate::utils::{Ptr, Transform};
use crate::{Float, PI}; use crate::{Float, PI};
use num_traits::Float as NumFloat;
#[repr(C)] #[repr(C)]
#[derive(Clone, Debug, Copy)] #[derive(Clone, Debug, Copy)]

View file

@ -7,6 +7,7 @@ use crate::core::spectrum::SpectrumTrait;
use crate::spectra::{DenselySampledSpectrum, SampledSpectrum, SampledWavelengths}; use crate::spectra::{DenselySampledSpectrum, SampledSpectrum, SampledWavelengths};
use crate::utils::Ptr; use crate::utils::Ptr;
use crate::{Float, PI}; use crate::{Float, PI};
use num_traits::Float as NumFloat;
#[repr(C)] #[repr(C)]
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug)]

View file

@ -20,7 +20,7 @@ use crate::utils::sampling::{
}; };
use crate::utils::{Ptr, Transform}; use crate::utils::{Ptr, Transform};
use crate::{Float, PI}; use crate::{Float, PI};
use std::sync::Arc; use num_traits::Float as NumFloat;
#[repr(C)] #[repr(C)]
#[derive(Debug, Copy, Clone)] #[derive(Debug, Copy, Clone)]

View file

@ -9,6 +9,7 @@ use crate::core::spectrum::SpectrumTrait;
use crate::spectra::{DenselySampledSpectrum, SampledSpectrum, SampledWavelengths}; use crate::spectra::{DenselySampledSpectrum, SampledSpectrum, SampledWavelengths};
use crate::utils::Ptr; use crate::utils::Ptr;
use crate::{Float, PI}; use crate::{Float, PI};
use num_traits::Float as NumFloat;
#[repr(C)] #[repr(C)]
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug)]

View file

@ -15,6 +15,7 @@ use crate::{
spectra::{RGBColorSpace, RGBIlluminantSpectrum}, spectra::{RGBColorSpace, RGBIlluminantSpectrum},
utils::{Ptr, Transform, sampling::DevicePiecewiseConstant2D}, utils::{Ptr, Transform, sampling::DevicePiecewiseConstant2D},
}; };
use num_traits::Float as NumFloat;
#[repr(C)] #[repr(C)]
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug)]

View file

@ -10,6 +10,7 @@ use crate::utils::ptr::Ptr;
use crate::utils::sampling::AliasTable; use crate::utils::sampling::AliasTable;
use crate::{Float, ONE_MINUS_EPSILON, PI}; use crate::{Float, ONE_MINUS_EPSILON, PI};
use enum_dispatch::enum_dispatch; use enum_dispatch::enum_dispatch;
use num_traits::Float as NumFloat;
#[derive(Clone, Copy, Debug, Default)] #[derive(Clone, Copy, Debug, Default)]
#[repr(C)] #[repr(C)]
@ -26,7 +27,7 @@ pub struct CompactLightBounds {
pub qb: [[u16; 3]; 2], pub qb: [[u16; 3]; 2],
} }
const _: () = assert!(std::mem::size_of::<CompactLightBounds>() == 24); const _: () = assert!(core::mem::size_of::<CompactLightBounds>() == 24);
impl CompactLightBounds { impl CompactLightBounds {
pub fn new(lb: &LightBounds, all_b: &Bounds3f) -> Self { pub fn new(lb: &LightBounds, all_b: &Bounds3f) -> Self {
@ -291,7 +292,7 @@ pub struct LightBVHNode {
packed_data: u32, packed_data: u32,
} }
const _: () = assert!(std::mem::size_of::<LightBVHNode>() == 32); const _: () = assert!(core::mem::size_of::<LightBVHNode>() == 32);
impl LightBVHNode { impl LightBVHNode {
/// Mask to isolate the Leaf Flag (Bit 31) /// Mask to isolate the Leaf Flag (Bit 31)

View file

@ -7,6 +7,7 @@ use crate::core::spectrum::SpectrumTrait;
use crate::spectra::{DenselySampledSpectrum, SampledSpectrum, SampledWavelengths}; use crate::spectra::{DenselySampledSpectrum, SampledSpectrum, SampledWavelengths};
use crate::utils::Ptr; use crate::utils::Ptr;
use crate::{Float, PI}; use crate::{Float, PI};
use num_traits::Float as NumFloat;
#[repr(C)] #[repr(C)]
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug)]

View file

@ -28,6 +28,7 @@ pub struct CoatedDiffuseMaterial {
pub max_depth: u32, pub max_depth: u32,
pub n_samples: u32, pub n_samples: u32,
pub remap_roughness: bool, pub remap_roughness: bool,
pub seed: i32,
} }
impl CoatedDiffuseMaterial { impl CoatedDiffuseMaterial {
@ -45,6 +46,7 @@ impl CoatedDiffuseMaterial {
remap_roughness: bool, remap_roughness: bool,
max_depth: u32, max_depth: u32,
n_samples: u32, n_samples: u32,
seed: i32,
) -> Self { ) -> Self {
Self { Self {
displacement, displacement,
@ -59,6 +61,7 @@ impl CoatedDiffuseMaterial {
remap_roughness, remap_roughness,
max_depth, max_depth,
n_samples, n_samples,
seed,
} }
} }
} }
@ -112,6 +115,7 @@ impl MaterialTrait for CoatedDiffuseMaterial {
gg, gg,
self.max_depth, self.max_depth,
self.n_samples, self.n_samples,
self.seed,
)); ));
BSDF::new(ctx.ns, ctx.dpdus, Ptr::from(&bxdf)) BSDF::new(ctx.ns, ctx.dpdus, Ptr::from(&bxdf))
@ -165,6 +169,7 @@ pub struct CoatedConductorMaterial {
max_depth: u32, max_depth: u32,
n_samples: u32, n_samples: u32,
remap_roughness: bool, remap_roughness: bool,
seed: i32,
} }
impl CoatedConductorMaterial { impl CoatedConductorMaterial {
@ -186,6 +191,7 @@ impl CoatedConductorMaterial {
max_depth: u32, max_depth: u32,
n_samples: u32, n_samples: u32,
remap_roughness: bool, remap_roughness: bool,
seed: i32,
) -> Self { ) -> Self {
Self { Self {
displacement, displacement,
@ -204,6 +210,7 @@ impl CoatedConductorMaterial {
remap_roughness, remap_roughness,
max_depth, max_depth,
n_samples, n_samples,
seed,
} }
} }
} }
@ -279,6 +286,7 @@ impl MaterialTrait for CoatedConductorMaterial {
gg, gg,
self.max_depth, self.max_depth,
self.n_samples, self.n_samples,
self.seed,
)); ));
BSDF::new(ctx.ns, ctx.dpdus, Ptr::from(&bxdf)) BSDF::new(ctx.ns, ctx.dpdus, Ptr::from(&bxdf))
} }
@ -302,20 +310,24 @@ impl MaterialTrait for CoatedConductorMaterial {
self.conductor_vroughness, 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() { 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() { if !self.k.is_null() {
spectrum_textures.push(self.k); spectrum_textures[n] = self.k;
n += 1;
} }
if !self.conductor_eta.is_null() { if !self.conductor_eta.is_null() {
spectrum_textures.push(self.reflectance); spectrum_textures[n] = self.reflectance;
} }
tex_eval.can_evaluate(&float_textures, &spectrum_textures) tex_eval.can_evaluate(&float_textures, &spectrum_textures)

View file

@ -10,6 +10,7 @@ use crate::utils::splines::{
bound_cubic_bezier, cubic_bezier_control_points, evaluate_cubic_bezier, subdivide_cubic_bezier, bound_cubic_bezier, cubic_bezier_control_points, evaluate_cubic_bezier, subdivide_cubic_bezier,
}; };
use crate::utils::transform::{Transform, look_at}; use crate::utils::transform::{Transform, look_at};
use num_traits::Float as NumFloat;
#[repr(C)] #[repr(C)]
#[derive(Debug, Clone, Copy, PartialEq)] #[derive(Debug, Clone, Copy, PartialEq)]

View file

@ -12,10 +12,11 @@ use crate::utils::splines::{
use crate::utils::transform::{Transform, look_at}; use crate::utils::transform::{Transform, look_at};
use crate::{Float, PI, gamma}; 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::interval::Interval;
use crate::utils::math::{clamp, difference_of_products, lerp, square}; use crate::utils::math::{clamp, difference_of_products, lerp, square};
use std::mem; use core::mem;
use num_traits::Float as NumFloat;
#[repr(C)] #[repr(C)]
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
@ -68,13 +69,13 @@ impl CylinderShape {
let f = b / (2. * a); let f = b / (2. * a);
let vx: Interval = oi.x() - f * di.x(); let vx: Interval = oi.x() - f * di.x();
let vy: Interval = oi.y() - f * di.y(); 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 = let discrim: Interval =
4. * a * (Interval::new(self.radius) * length) * (Interval::new(self.radius) - length); 4. * a * (Interval::new(self.radius) * length) * (Interval::new(self.radius) - length);
if discrim.low < 0. { if discrim.low < 0. {
return None; return None;
} }
let root_discrim = discrim.sqrt(); let root_discrim = discrim.sqrt_ext();
let q = if Float::from(b) < 0. { let q = if Float::from(b) < 0. {
-0.5 * (b - root_discrim) -0.5 * (b - root_discrim)
} else { } else {

View file

@ -10,7 +10,7 @@ use crate::utils::Transform;
use crate::utils::math::square; use crate::utils::math::square;
use crate::utils::sampling::sample_uniform_disk_concentric; use crate::utils::sampling::sample_uniform_disk_concentric;
use crate::{Float, PI}; use crate::{Float, PI};
use std::sync::Arc; use num_traits::Float as NumFloat;
#[repr(C)] #[repr(C)]
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]

View file

@ -2,7 +2,7 @@ use crate::core::geometry::{
Bounds3f, DirectionCone, Normal3f, Point2f, Point3f, Point3fi, Ray, Vector2f, Vector3f, Bounds3f, DirectionCone, Normal3f, Point2f, Point3f, Point3fi, Ray, Vector2f, Vector3f,
Vector3fi, VectorLike, 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::interaction::{Interaction, InteractionTrait, SurfaceInteraction};
use crate::core::pbrt::gamma; use crate::core::pbrt::gamma;
use crate::core::shape::{ 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::math::{clamp, difference_of_products, radians, safe_acos, safe_sqrt, square};
use crate::utils::sampling::sample_uniform_sphere; use crate::utils::sampling::sample_uniform_sphere;
use crate::{Float, PI}; use crate::{Float, PI};
use num_traits::Float as NumFloat;
use std::mem; use core::mem;
use std::sync::Arc;
#[repr(C)] #[repr(C)]
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
@ -94,7 +94,7 @@ impl SphereShape {
return None; return None;
} }
let root_discrim = discrim.sqrt(); let root_discrim = discrim.sqrt_ext();
let q = if Float::from(b) < 0. { let q = if Float::from(b) < 0. {
-0.5 * (b - root_discrim) -0.5 * (b - root_discrim)

View file

@ -3,7 +3,7 @@ use crate::core::geometry::{
Bounds3f, DirectionCone, Normal, Normal3f, Point2f, Point3f, Point3fi, Ray, Vector2f, Vector3, Bounds3f, DirectionCone, Normal, Normal3f, Point2f, Point3f, Point3fi, Ray, Vector2f, Vector3,
Vector3f, 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::{ use crate::core::interaction::{
Interaction, InteractionBase, InteractionTrait, SimpleInteraction, SurfaceInteraction, Interaction, InteractionBase, InteractionTrait, SimpleInteraction, SurfaceInteraction,
}; };

View file

@ -4,9 +4,7 @@ use crate::core::pbrt::Float;
use crate::spectra::{DenselySampledSpectrum, SampledSpectrum}; use crate::spectra::{DenselySampledSpectrum, SampledSpectrum};
use crate::utils::math::SquareMatrix3f; use crate::utils::math::SquareMatrix3f;
use crate::utils::ptr::Ptr; use crate::utils::ptr::Ptr;
use anyhow::{Result, anyhow}; use core::cmp::{Eq, PartialEq};
use std::cmp::{Eq, PartialEq};
#[repr(C)] #[repr(C)]
#[derive(Copy, Debug, Clone)] #[derive(Copy, Debug, Clone)]
@ -19,13 +17,14 @@ pub struct DeviceStandardColorSpaces {
impl DeviceStandardColorSpaces { impl DeviceStandardColorSpaces {
#[cfg(not(target_arch = "nvptx64"))] #[cfg(not(target_arch = "nvptx64"))]
pub fn get_named(&self, name: &str) -> Result<Ptr<RGBColorSpace>> { pub fn get_named(&self, name: &str) -> Option<Ptr<RGBColorSpace>> {
match name.to_lowercase().as_str() { let lower = name.as_bytes();
"srgb" => Ok(self.srgb), match lower {
"dci-p3" => Ok(self.dci_p3), b if b.eq_ignore_ascii_case(b"srgb") => Some(self.srgb),
"rec2020" => Ok(self.rec2020), b if b.eq_ignore_ascii_case(b"dci-p3") => Some(self.dci_p3),
"aces2065-1" => Ok(self.aces2065_1), b if b.eq_ignore_ascii_case(b"rec2020") => Some(self.rec2020),
_ => Err(anyhow!("No such spectrum")), b if b.eq_ignore_ascii_case(b"aces2065-1") => Some(self.aces2065_1),
_ => None,
} }
} }
@ -51,11 +50,12 @@ pub enum ColorSpaceId {
impl ColorSpaceId { impl ColorSpaceId {
#[cfg(not(target_arch = "nvptx64"))] #[cfg(not(target_arch = "nvptx64"))]
pub fn from_name(name: &str) -> Option<Self> { pub fn from_name(name: &str) -> Option<Self> {
match name.to_lowercase().as_str() { let lower = name.as_bytes();
"srgb" => Some(Self::SRGB), match lower {
"dci-p3" => Some(Self::DciP3), b if b.eq_ignore_ascii_case(b"srgb") => Some(Self::SRGB),
"rec2020" => Some(Self::Rec2020), b if b.eq_ignore_ascii_case(b"dci-p3") => Some(Self::DciP3),
"aces2065-1" => Some(Self::Aces2065_1), 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, _ => None,
} }
} }

View file

@ -1,9 +1,10 @@
use crate::core::pbrt::Float; use crate::core::pbrt::Float;
use crate::core::spectrum::{SpectrumTrait, StandardSpectra}; use crate::core::spectrum::{SpectrumTrait, StandardSpectra};
use crate::utils::math::{clamp, lerp}; use crate::utils::math::{clamp, lerp};
use std::ops::{ use core::ops::{
Add, AddAssign, Div, DivAssign, Index, IndexMut, Mul, MulAssign, Neg, Sub, SubAssign, 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; pub const CIE_Y_INTEGRAL: Float = 106.856895;
@ -42,7 +43,7 @@ impl SampledSpectrum {
F: FnMut(usize) -> Float, F: FnMut(usize) -> Float,
{ {
Self { 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 { impl<'a> IntoIterator for &'a SampledSpectrum {
type Item = &'a Float; type Item = &'a Float;
type IntoIter = std::slice::Iter<'a, Float>; type IntoIter = core::slice::Iter<'a, Float>;
fn into_iter(self) -> Self::IntoIter { fn into_iter(self) -> Self::IntoIter {
self.values.iter() self.values.iter()
@ -139,7 +140,7 @@ impl<'a> IntoIterator for &'a SampledSpectrum {
impl<'a> IntoIterator for &'a mut SampledSpectrum { impl<'a> IntoIterator for &'a mut SampledSpectrum {
type Item = &'a mut Float; 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 { fn into_iter(self) -> Self::IntoIter {
self.values.iter_mut() self.values.iter_mut()

View file

@ -6,8 +6,7 @@ use crate::spectra::{N_SPECTRUM_SAMPLES, SampledSpectrum, SampledWavelengths};
use crate::utils::find_interval; use crate::utils::find_interval;
use crate::utils::ptr::Ptr; use crate::utils::ptr::Ptr;
use core::slice; use core::slice;
use std::hash::{Hash, Hasher}; use num_traits::Float as NumFloat;
use std::sync::LazyLock;
#[repr(C)] #[repr(C)]
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
@ -68,17 +67,6 @@ impl PartialEq for DenselySampledSpectrum {
impl Eq for DenselySampledSpectrum {} impl Eq for DenselySampledSpectrum {}
// impl Hash for DenselySampledSpectrum {
// fn hash<H: Hasher>(&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 { impl SpectrumTrait for DenselySampledSpectrum {
fn sample(&self, lambda: &SampledWavelengths) -> SampledSpectrum { fn sample(&self, lambda: &SampledWavelengths) -> SampledSpectrum {
let mut s = SampledSpectrum::default(); let mut s = SampledSpectrum::default();

View file

@ -5,6 +5,7 @@ use crate::core::texture::{
}; };
use crate::spectra::{SampledSpectrum, SampledWavelengths}; use crate::spectra::{SampledSpectrum, SampledWavelengths};
use crate::utils::{Ptr, math::square}; use crate::utils::{Ptr, math::square};
use num_traits::Float as NumFloat;
fn checkerboard( fn checkerboard(
ctx: &TextureEvalContext, ctx: &TextureEvalContext,

View file

@ -7,6 +7,7 @@ use crate::spectra::sampled::{SampledSpectrum, SampledWavelengths};
use crate::utils::Ptr; use crate::utils::Ptr;
use crate::utils::math::square; use crate::utils::math::square;
use crate::utils::noise::noise_2d; use crate::utils::noise::noise_2d;
use num_traits::Float as NumFloat;
fn inside_polka_dot(st: Point2f) -> bool { fn inside_polka_dot(st: Point2f) -> bool {
let s_cell = (st[0] + 0.5).floor(); let s_cell = (st[0] + 0.5).floor();

View file

@ -8,6 +8,7 @@ use crate::utils::math::clamp;
use crate::utils::noise::fbm; use crate::utils::noise::fbm;
use crate::utils::ptr::Ptr; use crate::utils::ptr::Ptr;
use crate::utils::splines::evaluate_cubic_bezier; use crate::utils::splines::evaluate_cubic_bezier;
use num_traits::Float as NumFloat;
#[repr(C)] #[repr(C)]
#[derive(Clone, Debug, Copy)] #[derive(Clone, Debug, Copy)]

115
shared/src/utils/complex.rs Normal file
View file

@ -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<Complex> for Float {
type Output = Complex;
fn mul(self, rhs: Complex) -> Complex {
Complex {
re: self * rhs.re,
im: self * rhs.im,
}
}
}
impl core::ops::Mul<Float> for Complex {
type Output = Complex;
fn mul(self, rhs: Float) -> Complex {
Complex {
re: self.re * rhs,
im: self.im * rhs,
}
}
}
impl core::ops::Sub<Complex> for Float {
type Output = Complex;
fn sub(self, rhs: Complex) -> Complex {
Complex {
re: self - rhs.re,
im: -rhs.im,
}
}
}
impl core::ops::Add<Complex> for Float {
type Output = Complex;
fn add(self, rhs: Complex) -> Complex {
Complex {
re: self + rhs.re,
im: rhs.im,
}
}
}
impl core::ops::Div<Complex> 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,
}
}
}

View file

@ -4,11 +4,7 @@ use crate::core::geometry::{
use crate::core::pbrt::Float; use crate::core::pbrt::Float;
use crate::utils::Ptr; use crate::utils::Ptr;
use crate::utils::math::lerp; use crate::utils::math::lerp;
use std::collections::HashSet; use core::ops::{Add, Index, IndexMut, Mul, Sub};
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};
pub trait Interpolatable: pub trait Interpolatable:
Copy + Default + Add<Output = Self> + Sub<Output = Self> + Mul<Float, Output = Self> Copy + Default + Add<Output = Self> + Sub<Output = Self> + Mul<Float, Output = Self>
@ -158,7 +154,7 @@ impl<T> SampledGrid<T> {
} }
pub fn bytes_allocated(&self) -> u32 { pub fn bytes_allocated(&self) -> u32 {
self.values_len * std::mem::size_of::<T>() as u32 self.values_len * core::mem::size_of::<T>() as u32
} }
pub fn x_size(&self) -> i32 { pub fn x_size(&self) -> i32 {

View file

@ -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 {}

View file

@ -1,6 +1,4 @@
use crate::core::pbrt::Float; use crate::Float;
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
const U32_TO_F32_SCALE: f32 = 1.0 / 4294967296.0; 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<T: ?Sized>(data: &T, seed: u64) -> u64 { pub fn hash_buffer<T: ?Sized>(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 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) murmur_hash_64a(bytes, seed)
} }
@ -72,7 +70,7 @@ macro_rules! hash_values {
// We use a temporary tuple or array to pack bits // We use a temporary tuple or array to pack bits
#[repr(C, packed)] #[repr(C, packed)]
struct PackedData { struct PackedData {
$(_field: Box<[u8]>),* // Phantom, logic below is simpler $(_field: Box<[u8]>),*
} }
// Calculate total size and create a byte buffer // Calculate total size and create a byte buffer

View file

@ -1,7 +1,7 @@
use crate::core::pbrt::Float; use crate::core::pbrt::Float;
use crate::utils::math::{next_float_down, next_float_up}; use crate::utils::math::{next_float_down, next_float_up};
use num_traits::Zero; use num_traits::Zero;
use std::ops::{Add, Div, Mul, Neg, Sub}; use core::ops::{Add, Div, Mul, Neg, Sub};
#[repr(C)] #[repr(C)]
#[derive(Debug, Copy, Clone, PartialEq)] #[derive(Debug, Copy, Clone, PartialEq)]

View file

@ -1,4 +1,3 @@
use super::error::{InversionError, LlsError};
use crate::core::color::{RGB, XYZ}; use crate::core::color::{RGB, XYZ};
use crate::core::geometry::{Lerp, MulAdd, Point, Point2f, Point2i, Vector, Vector3f, VectorLike}; 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}; 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::sobol::{SOBOL_MATRICES_32, VDC_SOBOL_MATRICES, VDC_SOBOL_MATRICES_INV};
use crate::utils::Ptr; 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 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] #[inline]
pub fn degrees(a: Float) -> Float { pub fn degrees(a: Float) -> Float {
@ -69,7 +66,7 @@ pub fn evaluate_polynomial(t: Float, coeffs: &[Float]) -> Float {
assert!(!coeffs.is_empty()); assert!(!coeffs.is_empty());
let mut result = coeffs[0]; let mut result = coeffs[0];
for &c in &coeffs[1..] { for &c in &coeffs[1..] {
result = t.mul_add(result, c); result = num_traits::Float::mul_add(t, result, c);
} }
result result
} }
@ -274,13 +271,13 @@ pub fn smooth_step(x: Float, a: Float, b: Float) -> Float {
pub fn linear_least_squares<const R: usize, const N: usize>( pub fn linear_least_squares<const R: usize, const N: usize>(
a: [[Float; R]; N], a: [[Float; R]; N],
b: [[Float; R]; N], b: [[Float; R]; N],
) -> Result<SquareMatrix<Float, R>, Box<dyn Error>> { ) -> Option<SquareMatrix<Float, R>> {
let am = Matrix::from(a); let am = Matrix::from(a);
let bm = Matrix::from(b); let bm = Matrix::from(b);
let ata = am.transpose() * am; let ata = am.transpose() * am;
let atb = am.transpose() * bm; let atb = am.transpose() * bm;
let at_ai = ata.inverse()?; let at_ai = ata.inverse()?;
Ok((at_ai * atb).transpose()) Some((at_ai * atb).transpose())
} }
pub fn newton_bisection<P>(mut x0: Float, mut x1: Float, mut f: P) -> Float pub fn newton_bisection<P>(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() (-(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 { pub fn gaussian_integral(x0: Float, x1: Float, mu: Float, sigma: Float) -> Float {
assert!(sigma > 0.); assert!(sigma > 0.);
let sigma_root2 = sigma * 1.414213562373095; 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 { pub fn sample_linear(u: Float, a: Float, b: Float) -> Float {
@ -1079,7 +1090,7 @@ impl<T, const R: usize, const C: usize> Matrix<T, R, C> {
where where
T: Clone + Zero, 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 } 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<T: Display, const R: usize, const C: usize> Display for Matrix<T, R, C> { impl<T: Display, const R: usize, const C: usize> Display for Matrix<T, R, C> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut col_widths = [0; C]; let mut col_widths = [0; C];
// Measure without allocation
for row in self.m.iter() { for row in self.m.iter() {
for (j, element) in row.iter().enumerate() { for (j, element) in row.iter().enumerate() {
let width = format!("{}", element).len(); let mut counter = CountingFmt(0);
if width > col_widths[j] { let _ = write!(counter, "{}", element); // Ignores errors, just counts
col_widths[j] = width; col_widths[j] = col_widths[j].max(counter.0);
}
} }
} }
for i in 0..R { for i in 0..R {
write!(f, "[")?; write!(f, "[")?;
#[allow(clippy::needless_range_loop)]
for j in 0..C { for j in 0..C {
write!(f, "{: >width$} ", self.m[i][j], width = col_widths[j])?; write!(f, "{: >width$} ", self.m[i][j], width = col_widths[j])?;
} }
@ -1238,8 +1258,8 @@ impl<T, const N: usize> SquareMatrix<T, N> {
T: Copy + Zero + One, T: Copy + Zero + One,
{ {
Self { Self {
m: std::array::from_fn(|i| { m: core::array::from_fn(|i| {
std::array::from_fn(|j| if i == j { T::one() } else { T::zero() }) core::array::from_fn(|j| if i == j { T::one() } else { T::zero() })
}), }),
} }
} }
@ -1302,9 +1322,9 @@ impl<T, const N: usize> SquareMatrix<T, N>
where where
T: NumFloat + Sum + Product + Copy, T: NumFloat + Sum + Product + Copy,
{ {
pub fn inverse(&self) -> Result<Self, InversionError> { pub fn inverse(&self) -> Option<Self> {
if N == 0 { if N == 0 {
return Err(InversionError::EmptyMatrix); return None;
} }
let mut mat = self.m; let mut mat = self.m;
@ -1322,7 +1342,7 @@ where
let pivot = mat[i][i]; let pivot = mat[i][i];
if pivot.is_zero() { if pivot.is_zero() {
return Err(InversionError::SingularMatrix); return None;
} }
let inv_pivot = T::one() / pivot; let inv_pivot = T::one() / pivot;
@ -1344,7 +1364,8 @@ where
} }
} }
} }
Ok(inv)
Some(inv)
} }
pub fn determinant(&self) -> T { pub fn determinant(&self) -> T {
@ -1448,7 +1469,8 @@ where
{ {
type Output = Vector<T, N>; type Output = Vector<T, N>;
fn mul(self, rhs: Vector<T, N>) -> Self::Output { fn mul(self, rhs: Vector<T, N>) -> 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) Vector(arr)
} }
} }
@ -1459,7 +1481,8 @@ where
{ {
type Output = Point<T, N>; type Output = Point<T, N>;
fn mul(self, rhs: Point<T, N>) -> Self::Output { fn mul(self, rhs: Point<T, N>) -> 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) Point(arr)
} }
} }
@ -1540,31 +1563,7 @@ mod tests {
} }
} }
#[inline(always)] pub fn f16_to_f32_software(h: u16) -> f32 {
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::<u16, cuda_std::f16>(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 {
let sign = ((h >> 15) & 1) as u32; let sign = ((h >> 15) & 1) as u32;
let exp = ((h >> 10) & 0x1F) as u32; let exp = ((h >> 10) & 0x1F) as u32;
let mant = (h & 0x3FF) as u32; let mant = (h & 0x3FF) as u32;

View file

@ -1,10 +1,11 @@
pub mod complex;
pub mod containers; pub mod containers;
pub mod error;
pub mod hash; pub mod hash;
pub mod interval; pub mod interval;
pub mod math; pub mod math;
pub mod mesh; pub mod mesh;
pub mod noise; pub mod noise;
pub mod options;
pub mod ptr; pub mod ptr;
pub mod quaternion; pub mod quaternion;
pub mod rng; pub mod rng;
@ -13,11 +14,12 @@ pub mod sobol;
pub mod splines; pub mod splines;
pub mod transform; pub mod transform;
pub use options::PBRTOptions;
pub use ptr::Ptr; pub use ptr::Ptr;
pub use transform::{AnimatedTransform, Transform, TransformGeneric}; pub use transform::{AnimatedTransform, Transform, TransformGeneric};
use crate::Float; use crate::Float;
use core::sync::atomic::{AtomicU32, AtomicU64, Ordering}; use core::sync::atomic::{AtomicU32, Ordering};
#[inline] #[inline]
pub fn find_interval<F>(sz: u32, pred: F) -> u32 pub fn find_interval<F>(sz: u32, pred: F) -> u32
@ -113,37 +115,37 @@ impl AtomicFloat {
} }
} }
pub struct AtomicDouble { // pub struct AtomicDouble {
bits: AtomicU64, // bits: AtomicU64,
} // }
//
impl AtomicDouble { // impl AtomicDouble {
pub fn new(val: f64) -> Self { // pub fn new(val: f64) -> Self {
Self { // Self {
bits: AtomicU64::new(val.to_bits()), // bits: AtomicU64::new(val.to_bits()),
} // }
} // }
//
pub fn get(&self) -> f64 { // pub fn get(&self) -> f64 {
f64::from_bits(self.bits.load(Ordering::Relaxed)) // f64::from_bits(self.bits.load(Ordering::Relaxed))
} // }
//
pub fn add(&self, val: f64) { // pub fn add(&self, val: f64) {
let mut current_bits = self.bits.load(Ordering::Relaxed); // let mut current_bits = self.bits.load(Ordering::Relaxed);
loop { // loop {
let current_val = f64::from_bits(current_bits); // let current_val = f64::from_bits(current_bits);
let new_val = current_val + val; // let new_val = current_val + val;
let new_bits = new_val.to_bits(); // let new_bits = new_val.to_bits();
//
match self.bits.compare_exchange_weak( // match self.bits.compare_exchange_weak(
current_bits, // current_bits,
new_bits, // new_bits,
Ordering::Relaxed, // Ordering::Relaxed,
Ordering::Relaxed, // Ordering::Relaxed,
) { // ) {
Ok(_) => break, // Ok(_) => break,
Err(x) => current_bits = x, // Err(x) => current_bits = x,
} // }
} // }
} // }
} // }

View file

@ -1,6 +1,7 @@
use crate::Float; use crate::Float;
use crate::core::geometry::{Point3f, Vector3f, VectorLike}; use crate::core::geometry::{Point3f, Vector3f, VectorLike};
use crate::utils::math::{clamp, lerp, smooth_step}; use crate::utils::math::{clamp, lerp, smooth_step};
use num_traits::Float as NumFloat;
static NOISE_PERM_SIZE: usize = 256; static NOISE_PERM_SIZE: usize = 256;
static NOISE_PERM: [i32; 2 * NOISE_PERM_SIZE] = [ static NOISE_PERM: [i32; 2 * NOISE_PERM_SIZE] = [

View file

@ -1,7 +1,6 @@
use crate::core::geometry::{Bounds2f, Bounds2i, Point2f, Point2i};
use crate::Float; use crate::Float;
use std::ops::Deref; use crate::core::geometry::{Bounds2f, Bounds2i, Point2f, Point2i};
use std::sync::OnceLock; use core::ops::Deref;
#[derive(Debug, Clone, Copy, PartialEq, Eq)] #[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RenderingCoordinateSystem { pub enum RenderingCoordinateSystem {
@ -48,17 +47,17 @@ pub struct PBRTOptions {
pub basic: BasicPBRTOptions, pub basic: BasicPBRTOptions,
pub n_threads: usize, pub n_threads: usize,
pub log_level: String, pub log_level: &'static str,
pub write_partial_images: bool, pub write_partial_images: bool,
pub image_file: String, pub image_file: &'static str,
pub pixel_samples: Option<i32>, pub pixel_samples: Option<i32>,
pub gpu_device: Option<u32>, pub gpu_device: Option<u32>,
pub mse_reference_image: Option<String>, pub mse_reference_image: Option<&'static str>,
pub mse_reference_output: Option<String>, pub mse_reference_output: Option<&'static str>,
pub debug_start: Option<(Point2i, i32)>, pub debug_start: Option<(Point2i, i32)>,
pub quick_render: bool, pub quick_render: bool,
pub upgrade: bool, pub upgrade: bool,
pub display_server: String, pub display_server: &'static str,
pub crop_window: Option<Bounds2f>, pub crop_window: Option<Bounds2f>,
pub pixel_bounds: Option<Bounds2i>, pub pixel_bounds: Option<Bounds2i>,
pub pixel_material: Option<Point2i>, pub pixel_material: Option<Point2i>,
@ -70,17 +69,17 @@ impl Default for PBRTOptions {
Self { Self {
basic: BasicPBRTOptions::default(), basic: BasicPBRTOptions::default(),
n_threads: 0, n_threads: 0,
log_level: "info".to_string(), log_level: "info",
write_partial_images: false, write_partial_images: false,
pixel_samples: None, pixel_samples: None,
gpu_device: None, gpu_device: None,
quick_render: false, quick_render: false,
upgrade: false, upgrade: false,
image_file: "output.exr".to_string(), image_file: "output.exr",
mse_reference_image: None, mse_reference_image: None,
mse_reference_output: None, mse_reference_output: None,
debug_start: Some((Point2i::default(), 0)), debug_start: Some((Point2i::default(), 0)),
display_server: "".to_string(), display_server: "",
crop_window: None, crop_window: None,
pixel_bounds: None, pixel_bounds: None,
pixel_material: None, pixel_material: None,
@ -96,22 +95,3 @@ impl Deref for PBRTOptions {
&self.basic &self.basic
} }
} }
static OPTIONS: OnceLock<PBRTOptions> = 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<PBRTOptions> = OnceLock::new();
DEFAULT.get_or_init(PBRTOptions::default)
})
}

View file

@ -16,7 +16,7 @@ impl<T: ?Sized> Copy for Ptr<T> {}
impl<T: ?Sized> PartialEq for Ptr<T> { impl<T: ?Sized> PartialEq for Ptr<T> {
fn eq(&self, other: &Self) -> bool { 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<T: ?Sized + Sync> Sync for Ptr<T> {}
impl<T> Ptr<T> { impl<T> Ptr<T> {
pub const fn null() -> Self { pub const fn null() -> Self {
Self { Self {
ptr: std::ptr::null(), ptr: core::ptr::null(),
} }
} }
@ -91,7 +91,7 @@ impl<T> Ptr<T> {
if len == 0 { if len == 0 {
&[] &[]
} else { } else {
unsafe { std::slice::from_raw_parts(self.ptr, len) } unsafe { core::slice::from_raw_parts(self.ptr, len) }
} }
} }
@ -101,12 +101,12 @@ impl<T> Ptr<T> {
if self.is_null() { if self.is_null() {
if len == 0 { Some(&[]) } else { None } if len == 0 { Some(&[]) } else { None }
} else { } else {
Some(unsafe { std::slice::from_raw_parts(self.ptr, len) }) Some(unsafe { core::slice::from_raw_parts(self.ptr, len) })
} }
} }
} }
impl<T> std::ops::Deref for Ptr<T> { impl<T> core::ops::Deref for Ptr<T> {
type Target = T; type Target = T;
#[inline(always)] #[inline(always)]

View file

@ -1,5 +1,5 @@
use std::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Sub, SubAssign}; use core::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Sub, SubAssign};
use std::ops::{Index, IndexMut}; use core::ops::{Index, IndexMut};
use crate::core::geometry::{Vector3f, VectorLike}; use crate::core::geometry::{Vector3f, VectorLike};
use crate::utils::math::{safe_asin, sinx_over_x}; use crate::utils::math::{safe_asin, sinx_over_x};

View file

@ -1,8 +1,6 @@
use crate::check_rare;
use crate::core::geometry::{ use crate::core::geometry::{
Bounds2f, Frame, Point2f, Point2i, Point3f, Vector2f, Vector2i, Vector3f, VectorLike, 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::containers::DeviceArray2D;
use crate::utils::find_interval; use crate::utils::find_interval;
use crate::utils::math::{ use crate::utils::math::{
@ -12,8 +10,12 @@ use crate::utils::math::{
use crate::utils::ptr::Ptr; use crate::utils::ptr::Ptr;
use crate::utils::rng::Rng; 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 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; use num_traits::Num;
#[cfg(feature = "cpu_debug")]
use crate::{RARE_EVENT_CONDITION_MET, RARE_EVENT_TOTAL_CALLS, check_rare};
pub fn linear_pdf<T>(x: T, a: T, b: T) -> T pub fn linear_pdf<T>(x: T, a: T, b: T) -> T
where where
T: Num + Copy + PartialOrd, T: Num + Copy + PartialOrd,
@ -267,7 +269,7 @@ pub fn invert_spherical_rectangle_sample(
let b0sq = square(b0) + square(b1); let b0sq = square(b0) + square(b1);
let solid_angle_f64: f64 = 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; let solid_angle = solid_angle_f64 as Float;
if solid_angle < 1e-3 { if solid_angle < 1e-3 {
@ -289,6 +291,7 @@ pub fn invert_spherical_rectangle_sample(
let invcusq = 1. + z0sq / square(xu); let invcusq = 1. + z0sq / square(xu);
let fusq = invcusq - b0sq; let fusq = invcusq - b0sq;
let fu = fusq.sqrt().copysign(xu); let fu = fusq.sqrt().copysign(xu);
#[cfg(feature = "cpu_debug")]
check_rare!(1e-6, fu == 0.); check_rare!(1e-6, fu == 0.);
let sqrt = safe_sqrt(difference_of_products(b0, b0, b1, b1) + fusq); let sqrt = safe_sqrt(difference_of_products(b0, b0, b1, b1) + fusq);
let ay = -(b1 * fu) - (b0 * sqrt).copysign(fu * b0); let ay = -(b1 * fu) - (b0 * sqrt).copysign(fu * b0);

View file

@ -1,7 +1,6 @@
use core::iter::{Product, Sum};
use core::ops::{Add, Div, Index, IndexMut, Mul};
use num_traits::Float as NumFloat; 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::math::{SquareMatrix, radians, safe_acos};
use super::quaternion::Quaternion; use super::quaternion::Quaternion;
@ -13,8 +12,7 @@ use crate::core::geometry::{
use crate::core::interaction::{ use crate::core::interaction::{
Interaction, InteractionBase, InteractionTrait, MediumInteraction, SurfaceInteraction, Interaction, InteractionBase, InteractionTrait, MediumInteraction, SurfaceInteraction,
}; };
use crate::core::pbrt::{Float, gamma}; use crate::{Float, gamma};
use crate::utils::error::InversionError;
#[repr(C)] #[repr(C)]
#[derive(Debug, Copy, Clone)] #[derive(Debug, Copy, Clone)]
@ -28,15 +26,15 @@ impl<T: NumFloat + Sum + Product> TransformGeneric<T> {
Self { m, m_inv } Self { m, m_inv }
} }
pub fn from_matrix(m: SquareMatrix<T, 4>) -> Result<Self, InversionError> { pub fn from_matrix(m: SquareMatrix<T, 4>) -> Option<Self> {
let inv = m.inverse()?; let inv = m.inverse()?;
Ok(Self { m, m_inv: inv }) Some(Self { m, m_inv: inv })
} }
pub fn from_flat(flat: &[T; 16]) -> Result<Self, InversionError> { pub fn from_flat(flat: &[T; 16]) -> Option<Self> {
let m: SquareMatrix<T, 4> = SquareMatrix::from(flat); let m: SquareMatrix<T, 4> = SquareMatrix::from(flat);
let inv = m.inverse()?; let inv = m.inverse()?;
Ok(Self { m, m_inv: inv }) Some(Self { m, m_inv: inv })
} }
pub fn identity() -> Self { pub fn identity() -> Self {
@ -444,11 +442,7 @@ impl TransformGeneric<Float> {
Self { m, m_inv } Self { m, m_inv }
} }
pub fn perspective( pub fn perspective(fov: Float, n: Float, f: Float) -> Option<TransformGeneric<Float>> {
fov: Float,
n: Float,
f: Float,
) -> Result<TransformGeneric<Float>, InversionError> {
let persp: SquareMatrix<Float, 4> = SquareMatrix::new([ let persp: SquareMatrix<Float, 4> = SquareMatrix::new([
[1., 0., 0., 0.], [1., 0., 0., 0.],
[0., 1., 0., 0.], [0., 1., 0., 0.],
@ -457,7 +451,7 @@ impl TransformGeneric<Float> {
]); ]);
let inv_tan_ang = 1. / (radians(fov) / 2.).tan(); let inv_tan_ang = 1. / (radians(fov) / 2.).tan();
let persp_transform = TransformGeneric::from_matrix(persp)?; 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 { pub fn orthographic(z_near: Float, z_far: Float) -> Self {
@ -1971,11 +1965,11 @@ impl AnimatedTransform {
(c1, c2, c3, c4, c5) (c1, c2, c3, c4, c5)
} else { } else {
( (
std::array::from_fn(|_| DerivativeTerm::default()), core::array::from_fn(|_| DerivativeTerm::default()),
std::array::from_fn(|_| DerivativeTerm::default()), core::array::from_fn(|_| DerivativeTerm::default()),
std::array::from_fn(|_| DerivativeTerm::default()), core::array::from_fn(|_| DerivativeTerm::default()),
std::array::from_fn(|_| DerivativeTerm::default()), core::array::from_fn(|_| DerivativeTerm::default()),
std::array::from_fn(|_| DerivativeTerm::default()), core::array::from_fn(|_| DerivativeTerm::default()),
) )
}; };
AnimatedTransform { AnimatedTransform {
@ -2093,7 +2087,7 @@ pub fn look_at(
pos: impl Into<Point3f>, pos: impl Into<Point3f>,
look: impl Into<Point3f>, look: impl Into<Point3f>,
up: impl Into<Point3f>, up: impl Into<Point3f>,
) -> Result<TransformGeneric<Float>, InversionError> { ) -> Option<TransformGeneric<Float>> {
let mut world_from_camera: SquareMatrix<Float, 4> = SquareMatrix::default(); let mut world_from_camera: SquareMatrix<Float, 4> = SquareMatrix::default();
// Initialize fourth column of viewing matrix // Initialize fourth column of viewing matrix
let pos: Point3f = pos.into(); let pos: Point3f = pos.into();
@ -2133,5 +2127,5 @@ pub fn look_at(
world_from_camera[3][2] = 0.; world_from_camera[3][2] = 0.;
let camera_from_world = world_from_camera.inverse()?; 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))
} }

3
src/cameras/mod.rs Normal file
View file

@ -0,0 +1,3 @@
pub mod perspective;
pub mod realistic;
pub mod sherical;

View file

90
src/cameras/realistic.rs Normal file
View file

@ -0,0 +1,90 @@
use ash::vk::Image;
use shared::cameras::{Mapping, RealisticCamera, realistic::LensElementInterface};
struct RealisticCameraData {
aperture_image: Arc<Image>,
element_interfaces: Vec<LensElementInterface>,
}
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<Image>,
) -> 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<LensElementInterface> = 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 }
}
}

0
src/cameras/spherical.rs Normal file
View file

View file

@ -34,9 +34,9 @@ impl CameraBaseParameters {
medium: Arc<Medium>, medium: Arc<Medium>,
params: &ParameterDictionary, params: &ParameterDictionary,
loc: &FileLoc, loc: &FileLoc,
) -> Self { ) -> Result<Self> {
let mut shutter_open = params.get_one_float("shutteropen", 0.); let mut shutter_open = params.get_one_float("shutteropen", 0.)?;
let mut shutter_close = params.get_one_float("shutterclose", 1.); let mut shutter_close = params.get_one_float("shutterclose", 1.)?;
if shutter_close < shutter_open { if shutter_close < shutter_open {
eprint!( eprint!(
"{}: Shutter close time {} < shutter open {}. Swapping", "{}: Shutter close time {} < shutter open {}. Swapping",
@ -44,13 +44,14 @@ impl CameraBaseParameters {
); );
std::mem::swap(&mut shutter_open, &mut shutter_close); std::mem::swap(&mut shutter_open, &mut shutter_close);
} }
CameraBaseParameters {
Ok(CameraBaseParameters {
camera_transform: camera_transform.clone(), camera_transform: camera_transform.clone(),
shutter_open, shutter_open,
shutter_close, shutter_close,
film, film,
medium, medium,
} })
} }
} }
@ -120,14 +121,14 @@ impl CameraFactory for Camera {
"perspective" => { "perspective" => {
let full_res = film.full_resolution(); let full_res = film.full_resolution();
let camera_params = 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 base = CameraBase::create(camera_params);
let lens_radius = params.get_one_float("lensradius", 0.); let lens_radius = params.get_one_float("lensradius", 0.)?;
let focal_distance = params.get_one_float("focaldistance", 1e6); let focal_distance = params.get_one_float("focaldistance", 1e6)?;
let frame = params.get_one_float( let frame = params.get_one_float(
"frameaspectratio", "frameaspectratio",
full_res.x() as Float / full_res.y() as Float, full_res.x() as Float / full_res.y() as Float,
); )?;
let mut screen = if frame > 1. { let mut screen = if frame > 1. {
Bounds2f::from_points(Point2f::new(-frame, -1.), Point2f::new(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 !sw.is_empty() {
if get_options().fullscreen { if get_options().fullscreen {
eprint!("Screenwindow is ignored in fullscreen mode"); 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); let camera = PerspectiveCamera::new(base, fov, screen, lens_radius, focal_distance);
arena.alloc(camera); arena.alloc(camera);
@ -167,14 +168,14 @@ impl CameraFactory for Camera {
"orthographic" => { "orthographic" => {
let full_res = film.full_resolution(); let full_res = film.full_resolution();
let camera_params = 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 base = CameraBase::create(camera_params);
let lens_radius = params.get_one_float("lensradius", 0.); let lens_radius = params.get_one_float("lensradius", 0.)?;
let focal_distance = params.get_one_float("focaldistance", 1e6); let focal_distance = params.get_one_float("focaldistance", 1e6)?;
let frame = params.get_one_float( let frame = params.get_one_float(
"frameaspectratio", "frameaspectratio",
full_res.x() as Float / full_res.y() as Float, full_res.x() as Float / full_res.y() as Float,
); )?;
let mut screen = if frame > 1. { let mut screen = if frame > 1. {
Bounds2f::from_points(Point2f::new(-frame, -1.), Point2f::new(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 !sw.is_empty() {
if get_options().fullscreen { if get_options().fullscreen {
eprint!("Screenwindow is ignored in fullscreen mode"); eprint!("Screenwindow is ignored in fullscreen mode");
@ -209,11 +210,11 @@ impl CameraFactory for Camera {
} }
"realistic" => { "realistic" => {
let camera_params = 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 base = CameraBase::create(camera_params);
let aperture_diameter = params.get_one_float("aperturediameter", 1.); let aperture_diameter = params.get_one_float("aperturediameter", 1.)?;
let focal_distance = params.get_one_float("focaldistance", 10.); let focal_distance = params.get_one_float("focaldistance", 10.)?;
let lens_file = params.get_one_string("lensfile", ""); let lens_file = params.get_one_string("lensfile", "")?;
if lens_file.is_empty() { if lens_file.is_empty() {
return Err(anyhow!("{}: No lens file supplied", loc)); return Err(anyhow!("{}: No lens file supplied", loc));
@ -275,7 +276,7 @@ impl CameraFactory for Camera {
image image
}; };
let aperture_name = params.get_one_string("aperture", ""); let aperture_name = params.get_one_string("aperture", "")?;
let mut aperture_image: Option<Image> = None; let mut aperture_image: Option<Image> = None;
if !aperture_name.is_empty() { if !aperture_name.is_empty() {
@ -389,44 +390,9 @@ impl CameraFactory for Camera {
"spherical" => { "spherical" => {
let full_res = film.full_resolution(); let full_res = film.full_resolution();
let camera_params = 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 base = CameraBase::create(camera_params);
let lens_radius = params.get_one_float("lensradius", 0.); let m = params.get_one_string("mapping", "equalarea")?;
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 mapping = match m.as_str() { let mapping = match m.as_str() {
"equalarea" => Mapping::EqualArea, "equalarea" => Mapping::EqualArea,
"equirectangular" => Mapping::EquiRectangular, "equirectangular" => Mapping::EquiRectangular,

View file

@ -56,9 +56,9 @@ impl PixelSensor {
where where
Self: Sized, Self: Sized,
{ {
let iso = params.get_one_float("iso", 100.); let iso = params.get_one_float("iso", 100.)?;
let mut white_balance_temp = params.get_one_float("whitebalance", 0.); let mut white_balance_temp = params.get_one_float("whitebalance", 0.)?;
let sensor_name = params.get_one_string("sensor", "cie1931"); let sensor_name = params.get_one_string("sensor", "cie1931")?;
if sensor_name != "cie1931" && white_balance_temp == 0. { if sensor_name != "cie1931" && white_balance_temp == 0. {
white_balance_temp = 6500.; white_balance_temp = 6500.;
} }
@ -238,7 +238,9 @@ pub trait CreateFilmBase {
filter: Filter, filter: Filter,
sensor: Option<&DevicePixelSensor>, sensor: Option<&DevicePixelSensor>,
loc: &FileLoc, loc: &FileLoc,
) -> Self; ) -> Result<Self>
where
Self: Sized;
} }
impl CreateFilmBase for FilmBase { impl CreateFilmBase for FilmBase {
@ -247,9 +249,12 @@ impl CreateFilmBase for FilmBase {
filter: Filter, filter: Filter,
sensor: Option<&DevicePixelSensor>, sensor: Option<&DevicePixelSensor>,
loc: &FileLoc, loc: &FileLoc,
) -> Self { ) -> Result<Self>
let x_res = params.get_one_int("xresolution", 1280); where
let y_res = params.get_one_int("yresolution", 720); 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 { if x_res <= 0 || y_res <= 0 {
eprintln!( eprintln!(
@ -259,7 +264,7 @@ impl CreateFilmBase for FilmBase {
} }
let full_resolution = Point2i::new(x_res.max(1), y_res.max(1)); 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 { let crop = if crop_data.len() == 4 {
Bounds2f::from_points( Bounds2f::from_points(
Point2f::new(crop_data[0], crop_data[2]), 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); let expansion = Point2i::new(rad.x().ceil() as i32, rad.y().ceil() as i32);
pixel_bounds = pixel_bounds.expand(expansion); 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"); // let filename = params.get_one_string("filename", "pbrt.exr");
Self { Ok(Self {
full_resolution, full_resolution,
pixel_bounds, pixel_bounds,
filter, filter,
diagonal: diagonal_mm * 0.001, diagonal: diagonal_mm * 0.001,
sensor: Ptr::from(sensor.unwrap()), sensor: Ptr::from(sensor.unwrap()),
} })
} }
} }

View file

@ -2,53 +2,54 @@ use crate::filters::*;
use crate::utils::containers::Array2D; use crate::utils::containers::Array2D;
use crate::utils::sampling::PiecewiseConstant2D; use crate::utils::sampling::PiecewiseConstant2D;
use crate::utils::{FileLoc, ParameterDictionary}; use crate::utils::{FileLoc, ParameterDictionary};
use anyhow::{Result, anyhow};
use shared::Float; use shared::Float;
use shared::core::filter::{Filter, FilterSampler}; use shared::core::filter::{Filter, FilterSampler};
use shared::core::geometry::{Bounds2f, Point2f, Vector2f}; use shared::core::geometry::{Bounds2f, Point2f, Vector2f};
use shared::filters::*; use shared::filters::*;
pub trait FilterFactory { pub trait FilterFactory {
fn create(name: &str, params: &ParameterDictionary, loc: &FileLoc) -> Result<Filter, String>; fn create(name: &str, params: &ParameterDictionary, loc: &FileLoc) -> Result<Filter>;
} }
impl FilterFactory for Filter { impl FilterFactory for Filter {
fn create(name: &str, params: &ParameterDictionary, loc: &FileLoc) -> Result<Self, String> { fn create(name: &str, params: &ParameterDictionary, loc: &FileLoc) -> Result<Self> {
match name { match name {
"box" => { "box" => {
let xw = params.get_one_float("xradius", 0.5); let xw = params.get_one_float("xradius", 0.5)?;
let yw = params.get_one_float("yradius", 0.5); let yw = params.get_one_float("yradius", 0.5)?;
let filter = BoxFilter::new(Vector2f::new(xw, yw)); let filter = BoxFilter::new(Vector2f::new(xw, yw));
Ok(Filter::Box(filter)) Ok(Filter::Box(filter))
} }
"gaussian" => { "gaussian" => {
let xw = params.get_one_float("xradius", 1.5); let xw = params.get_one_float("xradius", 1.5)?;
let yw = params.get_one_float("yradius", 1.5); let yw = params.get_one_float("yradius", 1.5)?;
let sigma = params.get_one_float("sigma", 0.5); let sigma = params.get_one_float("sigma", 0.5)?;
let filter = GaussianFilter::new(Vector2f::new(xw, yw), sigma); let filter = GaussianFilter::new(Vector2f::new(xw, yw), sigma);
Ok(Filter::Gaussian(filter)) Ok(Filter::Gaussian(filter))
} }
"mitchell" => { "mitchell" => {
let xw = params.get_one_float("xradius", 2.); let xw = params.get_one_float("xradius", 2.)?;
let yw = params.get_one_float("yradius", 2.); let yw = params.get_one_float("yradius", 2.)?;
let b = params.get_one_float("B", 1. / 3.); let b = params.get_one_float("B", 1. / 3.)?;
let c = params.get_one_float("C", 1. / 3.); let c = params.get_one_float("C", 1. / 3.)?;
let filter = MitchellFilter::new(Vector2f::new(xw, yw), b, c); let filter = MitchellFilter::new(Vector2f::new(xw, yw), b, c);
Ok(Filter::Mitchell(filter)) Ok(Filter::Mitchell(filter))
} }
"sinc" => { "sinc" => {
let xw = params.get_one_float("xradius", 4.); let xw = params.get_one_float("xradius", 4.)?;
let yw = params.get_one_float("yradius", 4.); let yw = params.get_one_float("yradius", 4.)?;
let tau = params.get_one_float("tau", 3.); let tau = params.get_one_float("tau", 3.)?;
let filter = LanczosSincFilter::new(Vector2f::new(xw, yw), tau); let filter = LanczosSincFilter::new(Vector2f::new(xw, yw), tau);
Ok(Filter::LanczosSinc(filter)) Ok(Filter::LanczosSinc(filter))
} }
"triangle" => { "triangle" => {
let xw = params.get_one_float("xradius", 2.); let xw = params.get_one_float("xradius", 2.)?;
let yw = params.get_one_float("yradius", 2.); let yw = params.get_one_float("yradius", 2.)?;
let filter = TriangleFilter::new(Vector2f::new(xw, yw)); let filter = TriangleFilter::new(Vector2f::new(xw, yw));
Ok(Filter::Triangle(filter)) Ok(Filter::Triangle(filter))
} }
_ => Err(format!("Film type '{}' unknown at {}", name, loc)), _ => Err(anyhow!("Film type '{}' unknown at {}", name, loc)),
} }
} }
} }

71
src/core/interaction.rs Normal file
View file

@ -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<BSDF> {
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<BSSRDF> {
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 {}

View file

@ -5,6 +5,7 @@ pub mod color;
pub mod film; pub mod film;
pub mod filter; pub mod filter;
pub mod image; pub mod image;
pub mod interaction;
pub mod light; pub mod light;
pub mod material; pub mod material;
pub mod medium; pub mod medium;

View file

@ -4,9 +4,8 @@ use crate::Arena;
use crate::spectra::get_colorspace_device; use crate::spectra::get_colorspace_device;
use crate::utils::error::FileLoc; use crate::utils::error::FileLoc;
use crate::utils::normalize_utf8; use crate::utils::normalize_utf8;
use crate::utils::parameters::error_exit;
use crate::utils::parameters::{ParameterDictionary, ParsedParameterVector}; use crate::utils::parameters::{ParameterDictionary, ParsedParameterVector};
use crate::utils::parser::ParserTarget; use crate::utils::parser::{ParserError, ParserTarget};
use shared::Float; use shared::Float;
use shared::core::camera::CameraTransform; use shared::core::camera::CameraTransform;
use shared::core::geometry::Vector3f; 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 { 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 { 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, ParserError> {
ParameterDictionary::new(params.clone(), self.graphics_state.color_space.clone())
.map_err(|e| ParserError::Generic(format!("parameter error: {}", e), loc.clone()))
}
}
impl From<anyhow::Error> for ParserError {
fn from(e: anyhow::Error) -> Self {
ParserError::Generic(e.to_string(), FileLoc::default())
} }
} }
impl ParserTarget for BasicSceneBuilder { impl ParserTarget for BasicSceneBuilder {
fn reverse_orientation(&mut self, loc: FileLoc) { fn reverse_orientation(&mut self, loc: FileLoc) -> Result<(), ParserError> {
self.verify_world("ReverseOrientation", &loc); self.verify_world("ReverseOrientation", &loc)?;
self.graphics_state.reverse_orientation = !self.graphics_state.reverse_orientation; 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 stdcs = get_colorspace_device();
let _ = match stdcs.get_named(name) { let _ = match stdcs.get_named(name) {
Ok(cs) => { Ok(cs) => {
@ -199,25 +222,43 @@ impl ParserTarget for BasicSceneBuilder {
eprintln!("Error: Color space '{}' unknown at {}", name, loc); 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()); 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)); let t = Transform::translate(Vector3f::new(dx, dy, dz));
self.for_active_transforms(|cur| cur * &t); 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)); let t = Transform::rotate_around_axis(angle, Vector3f::new(ax, ay, az));
self.for_active_transforms(|cur| cur * &t); 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); let t = Transform::scale(sx, sy, sz);
self.for_active_transforms(|cur| cur * &t); self.for_active_transforms(|cur| cur * &t);
Ok(())
} }
fn look_at( fn look_at(
@ -232,7 +273,7 @@ impl ParserTarget for BasicSceneBuilder {
uy: Float, uy: Float,
uz: Float, uz: Float,
loc: FileLoc, loc: FileLoc,
) { ) -> Result<(), ParserError> {
let result = transform::look_at((ex, ey, ez), (lx, ly, lz), (ux, uy, uz)); let result = transform::look_at((ex, ey, ez), (lx, ly, lz), (ux, uy, uz));
match result { match result {
Ok(t) => { Ok(t) => {
@ -242,9 +283,10 @@ impl ParserTarget for BasicSceneBuilder {
eprintln!("Error: {} at {}", e, loc); 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); let result = Transform::from_flat(m);
match result { match result {
Ok(t) => { Ok(t) => {
@ -254,9 +296,10 @@ impl ParserTarget for BasicSceneBuilder {
eprintln!("Error: {} at {}", e, loc); 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); let result = Transform::from_flat(m);
match result { match result {
Ok(t) => { Ok(t) => {
@ -266,14 +309,16 @@ impl ParserTarget for BasicSceneBuilder {
eprintln!("Error: {} at {}", e, loc); 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 self.named_coordinate_systems
.insert(name.to_string(), self.graphics_state.ctm); .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) { if let Some(saved_ctm) = self.named_coordinate_systems.get(name) {
self.graphics_state.ctm = *saved_ctm; self.graphics_state.ctm = *saved_ctm;
} else { } else {
@ -282,10 +327,16 @@ impl ParserTarget for BasicSceneBuilder {
name, loc name, loc
); );
} }
Ok(())
} }
fn camera(&mut self, name: &str, params: &ParsedParameterVector, loc: FileLoc) { fn camera(
self.verify_options("Camera", &loc); &mut self,
name: &str,
params: &ParsedParameterVector,
loc: FileLoc,
) -> Result<(), ParserError> {
self.verify_options("Camera", &loc)?;
let camera_from_world = self.graphics_state.ctm; 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); CameraTransform::from_world(animated_world_from_cam, rendering_space);
self.render_from_world = camera_from_world.t[0]; self.render_from_world = camera_from_world.t[0];
let parameters = let parameters = self.make_params(&params.clone(), &loc)?;
ParameterDictionary::new(params.clone(), self.graphics_state.color_space.clone());
self.current_camera = Some(CameraSceneEntity { self.current_camera = Some(CameraSceneEntity {
base: SceneEntity { base: SceneEntity {
@ -319,85 +369,119 @@ impl ParserTarget for BasicSceneBuilder {
medium: self.graphics_state.current_outside_medium.clone(), 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; 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; 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; self.graphics_state.active_transform_bits = Self::START_TRANSFORM_BITS;
Ok(())
} }
fn transform_times(&mut self, start: Float, end: Float, loc: FileLoc) { fn transform_times(
self.verify_options("TransformTimes", &loc); &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_start_time = start;
self.graphics_state.transform_end_time = end; 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!() todo!()
} }
fn pixel_filter(&mut self, name: &str, params: &ParsedParameterVector, loc: FileLoc) { fn pixel_filter(
let parameters = &mut self,
ParameterDictionary::new(params.clone(), self.graphics_state.color_space.clone()); name: &str,
self.verify_options("PixelFilter", &loc); params: &ParsedParameterVector,
loc: FileLoc,
) -> Result<(), ParserError> {
self.verify_options("PixelFilter", &loc)?;
let parameters = self.make_params(params, &loc)?;
self.current_filter = Some(SceneEntity { self.current_filter = Some(SceneEntity {
name: name.to_string(), name: name.to_string(),
loc, loc,
parameters, parameters,
}); });
Ok(())
} }
fn film(&mut self, type_name: &str, params: &ParsedParameterVector, loc: FileLoc) { fn film(
let parameters = &mut self,
ParameterDictionary::new(params.clone(), self.graphics_state.color_space.clone()); type_name: &str,
self.verify_options("Film", &loc); params: &ParsedParameterVector,
loc: FileLoc,
) -> Result<(), ParserError> {
self.verify_options("Film", &loc)?;
let parameters = self.make_params(params, &loc)?;
self.current_filter = Some(SceneEntity { self.current_filter = Some(SceneEntity {
name: type_name.to_string(), name: type_name.to_string(),
loc, loc,
parameters, parameters,
}); });
Ok(())
} }
fn accelerator(&mut self, name: &str, params: &ParsedParameterVector, loc: FileLoc) { fn accelerator(
let parameters = &mut self,
ParameterDictionary::new(params.clone(), self.graphics_state.color_space.clone()); name: &str,
self.verify_options("PixelFilter", &loc); params: &ParsedParameterVector,
self.current_filter = Some(SceneEntity { 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(), name: name.to_string(),
loc, loc,
parameters, parameters,
}); });
Ok(())
} }
fn integrator(&mut self, name: &str, params: &ParsedParameterVector, loc: FileLoc) { fn integrator(
let parameters = &mut self,
ParameterDictionary::new(params.clone(), self.graphics_state.color_space.clone()); name: &str,
self.verify_options("PixelFilter", &loc); params: &ParsedParameterVector,
self.current_filter = Some(SceneEntity { 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(), name: name.to_string(),
loc, loc,
parameters, 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); let curr_name = normalize_utf8(name);
self.verify_world("MakeNamedMaterial", &loc);
if !self.named_material_names.insert(curr_name.to_string()) { if !self.named_material_names.insert(curr_name.to_string()) {
eprintln!("Error: {}: named material '{}' redefined.", loc, name); return Err(ParserError::Generic(
return; format!("Named material '{}' redefined.", name),
loc,
));
} }
let parameters = self.make_params(params, &loc)?;
let parameters =
ParameterDictionary::new(params.clone(), self.graphics_state.color_space.clone());
let entity = SceneEntity { let entity = SceneEntity {
name: name.to_string(), name: name.to_string(),
loc, loc,
@ -405,29 +489,41 @@ impl ParserTarget for BasicSceneBuilder {
}; };
self.scene.add_named_material(&curr_name, entity); 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 inside = normalize_utf8(inside_name);
let outside = normalize_utf8(outside_name); let outside = normalize_utf8(outside_name);
self.graphics_state.current_inside_medium = inside; self.graphics_state.current_inside_medium = inside;
self.graphics_state.current_outside_medium = outside; self.graphics_state.current_outside_medium = outside;
Ok(())
} }
fn sampler(&mut self, name: &str, params: &ParsedParameterVector, loc: FileLoc) { fn sampler(
let parameters = &mut self,
ParameterDictionary::new(params.clone(), self.graphics_state.color_space.clone()); name: &str,
self.verify_options("Sampler", &loc); params: &ParsedParameterVector,
loc: FileLoc,
) -> Result<(), ParserError> {
self.verify_options("Sampler", &loc)?;
let parameters = self.make_params(&params.clone(), &loc)?;
self.current_sampler = Some(SceneEntity { self.current_sampler = Some(SceneEntity {
name: name.to_string(), name: name.to_string(),
loc, loc,
parameters, parameters,
}) });
Ok(())
} }
fn world_begin(&mut self, loc: FileLoc, arena: Arc<Arena>) { fn world_begin(&mut self, loc: FileLoc, arena: Arc<Arena>) -> Result<(), ParserError> {
self.verify_options("WorldBegin", &loc); self.verify_options("WorldBegin", &loc)?;
self.current_block = BlockState::WorldBlock; self.current_block = BlockState::WorldBlock;
for i in 0..MAX_TRANSFORMS { for i in 0..MAX_TRANSFORMS {
self.graphics_state.ctm[i] = Transform::default(); self.graphics_state.ctm[i] = Transform::default();
@ -457,23 +553,24 @@ impl ParserTarget for BasicSceneBuilder {
.expect("Accelerator not set before WorldBegin"), .expect("Accelerator not set before WorldBegin"),
arena, arena,
); );
Ok(())
} }
fn attribute_begin(&mut self, loc: FileLoc) { fn attribute_begin(&mut self, loc: FileLoc) -> Result<(), ParserError> {
self.verify_world("AttributeBegin", &loc); self.verify_world("AttributeBegin", &loc)?;
self.pushed_graphics_states self.pushed_graphics_states
.push(self.graphics_state.clone()); .push(self.graphics_state.clone());
self.push_stack.push(('a', loc)); self.push_stack.push(('a', loc));
Ok(())
} }
fn attribute_end(&mut self, loc: FileLoc) { fn attribute_end(&mut self, loc: FileLoc) -> Result<(), ParserError> {
self.verify_world("AttributeEnd", &loc); self.verify_world("AttributeEnd", &loc)?;
if self.pushed_graphics_states.is_empty() { if self.pushed_graphics_states.is_empty() {
log::error!( return Err(ParserError::Generic(
"[{:?}] Unmatched AttributeEnd encountered. Ignoring it.", "Unmatched AttributeEnd encountered".into(),
loc loc,
); ));
return;
} }
if let Some(state) = self.pushed_graphics_states.pop() { 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 let Some((kind, start_loc)) = self.push_stack.pop() {
if kind == 'o' { if kind == 'o' {
log::error!( return Err(ParserError::Generic(
"[{:?}] Mismatched nesting: open ObjectBegin from {} at AttributeEnd", format!("Mismatched nesting: open ObjectBegin from {}", start_loc),
loc, loc,
start_loc ));
);
std::process::exit(1);
} else { } else {
debug_assert_eq!(kind, 'a', "Expected AttributeBegin on the stack"); 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 { let current_attributes = match target {
"shape" => &mut self.graphics_state.shape_attributes, "shape" => &mut self.graphics_state.shape_attributes,
"light" => &mut self.graphics_state.light_attributes, "light" => &mut self.graphics_state.light_attributes,
@ -502,13 +604,14 @@ impl ParserTarget for BasicSceneBuilder {
"medium" => &mut self.graphics_state.medium_attributes, "medium" => &mut self.graphics_state.medium_attributes,
"texture" => &mut self.graphics_state.texture_attributes, "texture" => &mut self.graphics_state.texture_attributes,
_ => { _ => {
log::error!( return Err(ParserError::Generic(
"[{:?}] Unknown attribute target \"{}\". Must be \"shape\", \"light\", \ format!(
"Unknown attribute target \"{}\". Must be \"shape\", \"light\", \
\"material\", \"medium\", or \"texture\".", \"material\", \"medium\", or \"texture\".",
target
),
loc, loc,
target ));
);
return;
} }
}; };
@ -520,6 +623,7 @@ impl ParserTarget for BasicSceneBuilder {
current_attributes.push(p.clone()); current_attributes.push(p.clone());
} }
Ok(())
} }
fn texture( fn texture(
@ -530,23 +634,23 @@ impl ParserTarget for BasicSceneBuilder {
params: &ParsedParameterVector, params: &ParsedParameterVector,
loc: FileLoc, loc: FileLoc,
arena: Arc<Arena>, arena: Arc<Arena>,
) { ) -> Result<(), ParserError> {
let name = normalize_utf8(orig_name); let name = normalize_utf8(orig_name);
self.verify_world("Texture", &loc); self.verify_world("Texture", &loc);
let dict = ParameterDictionary::from_array( let dict = ParameterDictionary::from_array(
params.clone(), params.clone(),
&self.graphics_state.texture_attributes, &self.graphics_state.texture_attributes,
self.graphics_state.color_space.clone(), self.graphics_state.color_space.clone(),
); )?;
if type_name != "float" && type_name != "spectrum" { if type_name != "float" && type_name != "spectrum" {
error_exit( return Err(ParserError::Generic(
Some(&loc), format!(
&format!(
"{}: texture type unknown. Must be \"float\" or \"spectrum\".", "{}: texture type unknown. Must be \"float\" or \"spectrum\".",
tex_name tex_name
), ),
); loc,
));
} }
{ {
@ -557,7 +661,10 @@ impl ParserTarget for BasicSceneBuilder {
}; };
if names.contains(&name) { 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()); names.insert(name.to_string());
} }
@ -579,42 +686,74 @@ impl ParserTarget for BasicSceneBuilder {
self.scene self.scene
.add_spectrum_texture(name.to_string(), entity, arena); .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); self.verify_world("material", &loc);
let entity = SceneEntity { let entity = SceneEntity {
name: name.to_string(), name: name.to_string(),
loc, 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(); 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!() todo!()
} }
fn named_material(&mut self, _name: &str, _loc: FileLoc) {
fn named_material(&mut self, _name: &str, _loc: FileLoc) -> Result<(), ParserError> {
todo!() 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) { fn object_begin(&mut self, _name: &str, _loc: FileLoc) -> Result<(), ParserError> {
todo!() Ok(())
} }
fn object_end(&mut self, _loc: FileLoc) { fn object_end(&mut self, _loc: FileLoc) -> Result<(), ParserError> {
todo!() Ok(())
} }
fn object_instance(&mut self, _name: &str, _loc: FileLoc) { fn object_instance(&mut self, _name: &str, _loc: FileLoc) -> Result<(), ParserError> {
todo!() Ok(())
} }
fn end_of_files(&mut self) { fn end_of_files(&mut self) -> Result<(), ParserError> {
todo!() Ok(())
} }
} }

View file

@ -13,6 +13,7 @@ use crate::utils::parallel::{AsyncJob, run_async};
use crate::utils::parameters::{NamedTextures, ParameterDictionary, TextureParameterDictionary}; use crate::utils::parameters::{NamedTextures, ParameterDictionary, TextureParameterDictionary};
use crate::utils::{Upload, resolve_filename}; use crate::utils::{Upload, resolve_filename};
use crate::{Arena, FileLoc}; use crate::{Arena, FileLoc};
use anyhow::{Result, anyhow};
use parking_lot::Mutex; use parking_lot::Mutex;
use rayon::prelude::*; use rayon::prelude::*;
use shared::core::camera::Camera; use shared::core::camera::Camera;
@ -110,7 +111,7 @@ impl BasicScene {
integ: SceneEntity, integ: SceneEntity,
accel: SceneEntity, accel: SceneEntity,
arena: Arc<Arena>, arena: Arc<Arena>,
) { ) -> Result<()> {
*self.integrator.lock() = Some(integ); *self.integrator.lock() = Some(integ);
*self.accelerator.lock() = Some(accel); *self.accelerator.lock() = Some(accel);
@ -118,9 +119,10 @@ impl BasicScene {
*self.film_colorspace.lock() = Some(Arc::clone(cs)); *self.film_colorspace.lock() = Some(Arc::clone(cs));
} }
let filt = Filter::create(&filter.name, &filter.parameters, &filter.loc); let filter = Filter::create(&filter.name, &filter.parameters, &filter.loc)
let shutter_close = camera.base.parameters.get_one_float("shutterclose", 1.); .map_err(|e| anyhow!("Failed to create filter: {}", e))?;
let shutter_open = camera.base.parameters.get_one_float("shutteropen", 0.); 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 exposure_time = shutter_close - shutter_open;
let film_instance = Arc::new( let film_instance = Arc::new(
@ -128,12 +130,12 @@ impl BasicScene {
&film.name, &film.name,
&film.parameters, &film.parameters,
exposure_time, exposure_time,
filt.expect("Must have a filter"), filter,
Some(camera.camera_transform.clone()), Some(camera.camera_transform.clone()),
&film.loc, &film.loc,
&arena, &arena,
) )
.expect("Must have a film"), .map_err(|e| anyhow!("Failed to create film: {}", e))?,
); );
*self.film_state.lock() = SingletonState { *self.film_state.lock() = SingletonState {
@ -152,7 +154,7 @@ impl BasicScene {
&sampler.loc, &sampler.loc,
&arena_sampler, &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); self.sampler_state.lock().job = Some(sampler_job);
@ -170,9 +172,10 @@ impl BasicScene {
&camera.base.loc, &camera.base.loc,
&arena_camera, &arena_camera,
) )
.expect("Failed to create camera") .map_err(|e| anyhow!("Failed to create camera: {}", e))
}); });
self.camera_state.lock().job = Some(camera_job); self.camera_state.lock().job = Some(camera_job);
Ok(())
} }
pub fn add_named_material(&self, name: &str, material: SceneEntity) { 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_serial: impl FnOnce(&mut TextureState) -> &mut Vec<(String, TextureSceneEntity)>,
get_jobs: impl FnOnce(&mut TextureState) -> &mut HashMap<String, AsyncJob<Arc<T>>>, get_jobs: impl FnOnce(&mut TextureState) -> &mut HashMap<String, AsyncJob<Arc<T>>>,
create_fn: F, create_fn: F,
) where ) -> Result<()>
where
T: Send + Sync + 'static, T: Send + Sync + 'static,
F: FnOnce(TextureSceneEntity) -> T + Send + 'static, F: FnOnce(TextureSceneEntity) -> T + Send + 'static,
{ {
@ -209,23 +213,24 @@ impl BasicScene {
if texture.base.name != "imagemap" && texture.base.name != "ptex" { if texture.base.name != "imagemap" && texture.base.name != "ptex" {
get_serial(state).push((name, texture)); 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) if !self.validate_texture_file(&filename, &texture.base.loc, &mut state.n_missing_textures)
{ {
return; return Ok(());
} }
if state.loading_texture_filenames.contains(&filename) { if state.loading_texture_filenames.contains(&filename) {
get_serial(state).push((name, texture)); get_serial(state).push((name, texture));
return; return Ok(());
} }
state.loading_texture_filenames.insert(filename); state.loading_texture_filenames.insert(filename);
let job = run_async(move || Arc::new(create_fn(texture))); let job = run_async(move || Arc::new(create_fn(texture)));
get_jobs(state).insert(name, job); get_jobs(state).insert(name, job);
Ok(())
} }
fn validate_texture_file(&self, filename: &str, loc: &FileLoc, n_missing: &mut usize) -> bool { fn validate_texture_file(&self, filename: &str, loc: &FileLoc, n_missing: &mut usize) -> bool {
@ -245,7 +250,12 @@ impl BasicScene {
true true
} }
pub fn add_float_texture(&self, name: String, texture: TextureSceneEntity, arena: Arc<Arena>) { pub fn add_float_texture(
&self,
name: String,
texture: TextureSceneEntity,
arena: Arc<Arena>,
) -> Result<()> {
let mut state = self.texture_state.lock(); let mut state = self.texture_state.lock();
self.add_texture_generic( self.add_texture_generic(
name, name,
@ -265,7 +275,7 @@ impl BasicScene {
) )
.expect("Could not create Float texture") .expect("Could not create Float texture")
}, },
); )
} }
pub fn add_spectrum_texture( pub fn add_spectrum_texture(
@ -273,7 +283,7 @@ impl BasicScene {
name: String, name: String,
texture: TextureSceneEntity, texture: TextureSceneEntity,
arena: Arc<Arena>, arena: Arc<Arena>,
) { ) -> Result<()> {
let mut state = self.texture_state.lock(); let mut state = self.texture_state.lock();
self.add_texture_generic( self.add_texture_generic(
name, name,
@ -294,7 +304,7 @@ impl BasicScene {
) )
.expect("Could not create spectrum texture") .expect("Could not create spectrum texture")
}, },
); )
} }
pub fn add_area_light(&self, light: SceneEntity) -> usize { pub fn add_area_light(&self, light: SceneEntity) -> usize {
@ -378,16 +388,25 @@ impl BasicScene {
named 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( pub fn create_materials(
&self, &self,
textures: &NamedTextures, textures: &NamedTextures,
arena: &mut Arena, arena: &mut Arena,
) -> (HashMap<String, Material>, Vec<Material>) { ) -> Result<(HashMap<String, Material>, Vec<Material>)> {
let mut state = self.material_state.lock(); let mut state = self.material_state.lock();
let finished: Vec<_> = state.normal_map_jobs.drain().collect(); let finished: Vec<_> = state.normal_map_jobs.drain().collect();
for (filename, job) in finished { 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<String, Material> = HashMap::new(); let mut named_materials: HashMap<String, Material> = HashMap::new();
@ -402,57 +421,72 @@ impl BasicScene {
continue; continue;
} }
let mat_type = entity.parameters.get_one_string("type", ""); let mat_type = entity.parameters.get_one_string("type", "")?;
if mat_type.is_empty() { if mat_type.is_empty() {
log::error!( log::error!("{}: missing material type", entity.loc);
"{}: \"string type\" not provided in named material's parameters.",
entity.loc
);
continue; 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( let tex_dict = TextureParameterDictionary::new(
Arc::new(entity.parameters.clone()), Arc::new(entity.parameters.clone()),
Some(textures), Some(textures),
); );
let mat = Material::create( match Material::create(
&mat_type, &mat_type,
&tex_dict, &tex_dict,
normal_map, normal_map,
&named_materials, // Reference for now &named_materials,
entity.loc.clone(), entity.loc.clone(),
arena, arena,
) ) {
.expect("Could not create material"); Ok(mat) => {
named_materials.insert(name.clone(), mat);
named_materials.insert(name.clone(), mat); }
Err(e) => {
log::error!(
"{}: Failed to create material '{}': {}",
entity.loc,
name,
e
);
}
}
} }
let materials: Vec<Material> = state let materials: Vec<Material> = state
.materials .materials
.iter() .iter()
.map(|entity| { .filter_map(|entity| {
let normal_map = self.get_normal_map(&state, &entity.parameters); let result: Result<Material> = (|| {
let tex_dict = TextureParameterDictionary::new( let normal_map = self.get_normal_map(&state, &entity.parameters)?;
entity.parameters.clone().into(), let tex_dict = TextureParameterDictionary::new(
Some(textures), entity.parameters.clone().into(),
); Some(textures),
);
Material::create( Material::create(
&entity.name, &entity.name,
&tex_dict, &tex_dict,
normal_map, normal_map,
&named_materials, &named_materials,
entity.loc.clone(), entity.loc.clone(),
arena, arena,
) )
.expect("Could not create material") })();
match result {
Ok(mat) => Some(mat),
Err(e) => {
log::error!("{}: Failed to create material: {}", entity.loc, e);
None
}
}
}) })
.collect(); .collect();
(named_materials, materials) Ok((named_materials, materials))
} }
pub fn create_aggregate( pub fn create_aggregate(
@ -499,7 +533,7 @@ impl BasicScene {
) -> Vec<Vec<Shape>> { ) -> Vec<Vec<Shape>> {
entities entities
.par_iter() .par_iter()
.map(|sh| { .filter_map(|sh| {
Shape::create( Shape::create(
&sh.base.name, &sh.base.name,
*sh.render_from_object.as_ref(), *sh.render_from_object.as_ref(),
@ -510,7 +544,7 @@ impl BasicScene {
sh.base.loc.clone(), sh.base.loc.clone(),
arena, arena,
) )
.expect("Could not create shape") .ok()
}) })
.collect() .collect()
} }
@ -621,56 +655,57 @@ impl BasicScene {
Vec::new() Vec::new()
} }
// ========================================================================
// Getters // Getters
// ========================================================================
pub fn get_camera(&self) -> Arc<Camera> { pub fn get_camera(&self) -> Result<Arc<Camera>> {
self.get_singleton(&self.camera_state, "Camera") self.get_singleton(&self.camera_state, "Camera")
} }
pub fn get_sampler(&self) -> Arc<Sampler> { pub fn get_sampler(&self) -> Result<Arc<Sampler>> {
self.get_singleton(&self.sampler_state, "Sampler") self.get_singleton(&self.sampler_state, "Sampler")
} }
pub fn get_film(&self) -> Arc<Film> { pub fn get_film(&self) -> Result<Arc<Film>> {
self.get_singleton(&self.film_state, "Film") self.get_singleton(&self.film_state, "Film")
} }
// ========================================================================
// Helpers // Helpers
// ========================================================================
fn get_singleton<T: Send + 'static>( fn get_singleton<T: Send + 'static>(
&self, &self,
state: &Mutex<SingletonState<T>>, state: &Mutex<SingletonState<T>>,
name: &str, name: &str,
) -> Arc<T> { ) -> Result<Arc<T>> {
let mut guard = state.lock(); let mut guard = state.lock();
if let Some(ref res) = guard.result { if let Some(ref res) = guard.result {
return res.clone(); return Ok(res.clone());
} }
if let Some(job) = guard.job.take() { 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()); 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) { fn start_loading_normal_maps(
let filename = resolve_filename(&params.get_one_string("normalmap", "")); &self,
state: &mut MaterialState,
params: &ParameterDictionary,
) -> Result<()> {
let filename = resolve_filename(&params.get_one_string("normalmap", "")?);
if filename.is_empty() { if filename.is_empty() {
return; return Ok(());
} }
if state.normal_map_jobs.contains_key(&filename) if state.normal_map_jobs.contains_key(&filename)
|| state.normal_maps.contains_key(&filename) || state.normal_maps.contains_key(&filename)
{ {
return; return Ok(());
} }
let filename_clone = filename.clone(); let filename_clone = filename.clone();
@ -693,18 +728,19 @@ impl BasicScene {
}); });
state.normal_map_jobs.insert(filename, job); state.normal_map_jobs.insert(filename, job);
Ok(())
} }
fn get_normal_map( fn get_normal_map(
&self, &self,
state: &MaterialState, state: &MaterialState,
params: &ParameterDictionary, params: &ParameterDictionary,
) -> Option<Arc<Image>> { ) -> Result<Option<Arc<Image>>> {
let filename = resolve_filename(&params.get_one_string("normalmap", "")); let filename = resolve_filename(&params.get_one_string("normalmap", "")?);
if filename.is_empty() { 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( fn get_alpha_texture(

View file

@ -2,6 +2,7 @@ use super::{SceneEntity, TextureSceneEntity};
use crate::core::image::Image; use crate::core::image::Image;
use crate::core::texture::{FloatTexture, SpectrumTexture}; use crate::core::texture::{FloatTexture, SpectrumTexture};
use crate::utils::parallel::AsyncJob; use crate::utils::parallel::AsyncJob;
use anyhow::Result;
use shared::core::light::Light; use shared::core::light::Light;
use shared::core::medium::Medium; use shared::core::medium::Medium;
use std::collections::{HashMap, HashSet}; use std::collections::{HashMap, HashSet};
@ -41,7 +42,7 @@ pub struct MediaState {
#[derive(Debug)] #[derive(Debug)]
pub struct SingletonState<T> { pub struct SingletonState<T> {
pub result: Option<Arc<T>>, pub result: Option<Arc<T>>,
pub job: Option<AsyncJob<T>>, pub job: Option<AsyncJob<Result<T>>>,
} }
impl<T> Default for SingletonState<T> { impl<T> Default for SingletonState<T> {

View file

@ -165,7 +165,9 @@ pub trait CreateTextureMapping {
params: &TextureParameterDictionary, params: &TextureParameterDictionary,
render_from_texture: &Transform, render_from_texture: &Transform,
loc: &FileLoc, loc: &FileLoc,
) -> Self; ) -> Result<Self>
where
Self: Sized;
} }
impl CreateTextureMapping for TextureMapping2D { impl CreateTextureMapping for TextureMapping2D {
@ -173,36 +175,39 @@ impl CreateTextureMapping for TextureMapping2D {
params: &TextureParameterDictionary, params: &TextureParameterDictionary,
render_from_texture: &Transform, render_from_texture: &Transform,
loc: &FileLoc, loc: &FileLoc,
) -> Self { ) -> Result<Self>
let mtype = params.get_one_string("mapping", "uv"); where
Self: Sized,
{
let mtype = params.get_one_string("mapping", "uv")?;
match mtype.as_str() { match mtype.as_str() {
"uv" => { "uv" => {
let su = params.get_one_float("uscale", 1.); let su = params.get_one_float("uscale", 1.)?;
let sv = params.get_one_float("vscale", 1.); let sv = params.get_one_float("vscale", 1.)?;
let du = params.get_one_float("udelta", 0.); let du = params.get_one_float("udelta", 0.)?;
let dv = params.get_one_float("vdelta", 0.); let dv = params.get_one_float("vdelta", 0.)?;
let mapping = UVMapping::new(su, sv, du, dv); let mapping = UVMapping::new(su, sv, du, dv);
TextureMapping2D::UV(mapping) Ok(TextureMapping2D::UV(mapping))
} }
"spherical" => { "spherical" => {
let mapping = SphericalMapping::new(&render_from_texture.inverse()); let mapping = SphericalMapping::new(&render_from_texture.inverse());
TextureMapping2D::Spherical(mapping) Ok(TextureMapping2D::Spherical(mapping))
} }
"cylindrical" => { "cylindrical" => {
let mapping = CylindricalMapping::new(&render_from_texture.inverse()); let mapping = CylindricalMapping::new(&render_from_texture.inverse());
TextureMapping2D::Cylindrical(mapping) Ok(TextureMapping2D::Cylindrical(mapping))
} }
"planar" => { "planar" => {
let vs = params.get_one_vector3f("v1", Vector3f::new(1., 0., 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 vt = params.get_one_vector3f("v2", Vector3f::new(0., 1., 0.))?;
let ds = params.get_one_float("udelta", 0.); let ds = params.get_one_float("udelta", 0.)?;
let dt = params.get_one_float("vdelta", 0.); let dt = params.get_one_float("vdelta", 0.)?;
let mapping = PlanarMapping::new(&render_from_texture.inverse(), vs, vt, ds, dt); 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); log::error!("{}: 2D texture mapping unknown {}", loc, mtype);
TextureMapping2D::UV(UVMapping::default()) Ok(TextureMapping2D::UV(UVMapping::default()))
} }
} }
} }

View file

@ -57,17 +57,17 @@ impl CreateFilm for GBufferFilm {
_arena: &Arena, _arena: &Arena,
) -> Result<Film> { ) -> Result<Film> {
let colorspace = params.color_space.as_ref().unwrap(); let colorspace = params.color_space.as_ref().unwrap();
let max_component_value = params.get_one_float("maxcomponentvalue", Float::INFINITY); let max_component_value = params.get_one_float("maxcomponentvalue", Float::INFINITY)?;
let write_fp16 = params.get_one_bool("savefp16", true); let write_fp16 = params.get_one_bool("savefp16", true)?;
let sensor = PixelSensor::create(params, colorspace.clone(), exposure_time, loc)?; 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()) { if Path::new(&filename).extension() != Some("exr".as_ref()) {
return Err(anyhow!("{}: EXR is the only format supported by GBufferFilm", loc).into()); 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 mut apply_inverse = false;
let camera_transform = let camera_transform =
camera_transform.ok_or_else(|| anyhow!("GBufferFilm requires a camera_transform"))?; camera_transform.ok_or_else(|| anyhow!("GBufferFilm requires a camera_transform"))?;

View file

@ -70,10 +70,10 @@ impl CreateFilm for RGBFilm {
_arena: &Arena, _arena: &Arena,
) -> Result<Film> { ) -> Result<Film> {
let colorspace = params.color_space.as_ref().unwrap(); let colorspace = params.color_space.as_ref().unwrap();
let max_component_value = params.get_one_float("maxcomponentvalue", Float::INFINITY); let max_component_value = params.get_one_float("maxcomponentvalue", Float::INFINITY)?;
let write_fp16 = params.get_one_bool("savefp16", true); let write_fp16 = params.get_one_bool("savefp16", true)?;
let sensor = PixelSensor::create(params, colorspace.clone(), exposure_time, loc)?; 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); let film = RGBFilmHost::new(film_base, &colorspace, max_component_value, write_fp16);
Ok(Film::RGB(film.device)) Ok(Film::RGB(film.device))
} }

View file

@ -97,19 +97,19 @@ impl CreateFilm for SpectralFilm {
_arena: &Arena, _arena: &Arena,
) -> Result<Film> { ) -> Result<Film> {
let colorspace = params.color_space.as_ref().unwrap(); let colorspace = params.color_space.as_ref().unwrap();
let max_component_value = params.get_one_float("maxcomponentvalue", Float::INFINITY); let max_component_value = params.get_one_float("maxcomponentvalue", Float::INFINITY)?;
let write_fp16 = params.get_one_bool("savefp16", true); let write_fp16 = params.get_one_bool("savefp16", true)?;
let sensor = PixelSensor::create(params, colorspace.clone(), exposure_time, loc)?; 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()) { if Path::new(&filename).extension() != Some("exr".as_ref()) {
return Err(anyhow!("{}: EXR is the only format supported by GBufferFilm", loc).into()); 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 n_buckets = params.get_one_int("nbuckets", 16)? as usize;
let lambda_min = params.get_one_float("lambdamin", LAMBDA_MIN as Float); 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 lambda_max = params.get_one_float("lambdamin", LAMBDA_MAX as Float)?;
if lambda_min < LAMBDA_MIN as Float && lambda_max > LAMBDA_MAX as Float { if lambda_min < LAMBDA_MIN as Float && lambda_max > LAMBDA_MAX as Float {
return Err(anyhow!( return Err(anyhow!(
"{}: PBRT must be recompiled with different values of LAMBDA_MIN and LAMBDA_MAX", "{}: PBRT must be recompiled with different values of LAMBDA_MIN and LAMBDA_MAX",

View file

@ -15,10 +15,32 @@ impl LanczosFilterCreator for LanczosSincFilter {
windowed_sinc(p.x(), radius.x(), tau) * windowed_sinc(p.y(), radius.y(), tau) 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::<Float>()) / sqrt_samples as Float,
(y as Float + rng.random::<Float>()) / 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 { Self {
radius, radius,
tau, tau,
sampler, sampler,
integral,
} }
} }
} }

View file

@ -2,6 +2,23 @@ use crate::core::color::RGBToSpectrumTableData;
use bytemuck::cast_slice; use bytemuck::cast_slice;
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
use shared::Float; use shared::Float;
use shared::PBRTOptions;
use std::sync::OnceLock;
static OPTIONS: OnceLock<PBRTOptions> = 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<PBRTOptions> = OnceLock::new();
DEFAULT.get_or_init(PBRTOptions::default)
})
}
static SRGB_SCALE_BYTES: &[u8] = include_bytes!("../data/srgb_scale.dat"); static SRGB_SCALE_BYTES: &[u8] = include_bytes!("../data/srgb_scale.dat");
static SRGB_COEFFS_BYTES: &[u8] = include_bytes!("../data/srgb_coeffs.dat"); static SRGB_COEFFS_BYTES: &[u8] = include_bytes!("../data/srgb_coeffs.dat");

View file

@ -1,4 +1,6 @@
#![feature(f16)]
#[allow(dead_code)] #[allow(dead_code)]
pub mod cameras;
pub mod core; pub mod core;
pub mod films; pub mod films;
pub mod filters; pub mod filters;

View file

@ -114,10 +114,10 @@ impl CreateLight for DiffuseAreaLight {
) -> Result<Light> { ) -> Result<Light> {
let mut l = params.get_one_spectrum("l", None, SpectrumType::Illuminant); let mut l = params.get_one_spectrum("l", None, SpectrumType::Illuminant);
let illum_spec = Spectrum::Dense(colorspace.unwrap().illuminant); let illum_spec = Spectrum::Dense(colorspace.unwrap().illuminant);
let mut scale = params.get_one_float("scale", 1.); let mut scale = params.get_one_float("scale", 1.)?;
let two_sided = params.get_one_bool("twosided", false); let two_sided = params.get_one_bool("twosided", false)?;
let filename = resolve_filename(&params.get_one_string("filename", "")); let filename = resolve_filename(&params.get_one_string("filename", "")?);
let (image, image_color_space) = if !filename.is_empty() { let (image, image_color_space) = if !filename.is_empty() {
if l.is_some() { if l.is_some() {
return Err(anyhow!("{}: both \"L\" and \"filename\" specified", loc)); 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); let l_for_scale = l.as_ref().unwrap_or(&illum_spec);
scale /= spectrum_to_photometric(*l_for_scale); 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 { if phi_v > 0.0 {
// k_e is the emissive power of the light as defined by the spectral // k_e is the emissive power of the light as defined by the spectral
// distribution and texture and is used to normalize the emitted // distribution and texture and is used to normalize the emitted

View file

@ -54,10 +54,10 @@ impl CreateLight for DistantLight {
SpectrumType::Illuminant, SpectrumType::Illuminant,
) )
.unwrap(); .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 from = parameters.get_one_point3f("from", Point3f::new(0., 0., 0.))?;
let to = parameters.get_one_point3f("to", Point3f::new(0., 0., 1.)); let to = parameters.get_one_point3f("to", Point3f::new(0., 0., 1.))?;
let w = (from - to).normalize(); let w = (from - to).normalize();
let (v1, v2) = w.coordinate_system(); let (v1, v2) = w.coordinate_system();
let m: [Float; 16] = [ let m: [Float; 16] = [
@ -82,7 +82,7 @@ impl CreateLight for DistantLight {
let final_render = render_from_light * t; let final_render = render_from_light * t;
scale /= spectrum_to_photometric(l); scale /= spectrum_to_photometric(l);
// Adjust scale to meet target illuminance value // 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. { if e_v > 0. {
scale *= e_v; scale *= e_v;
} }

View file

@ -72,8 +72,8 @@ impl CreateLight for GoniometricLight {
SpectrumType::Illuminant, SpectrumType::Illuminant,
) )
.expect("Could not retrieve spectrum"); .expect("Could not retrieve spectrum");
let mut scale = params.get_one_float("scale", 1.); let mut scale = params.get_one_float("scale", 1.)?;
let filename = resolve_filename(&params.get_one_string("filename", "")); let filename = resolve_filename(&params.get_one_string("filename", "")?);
let image: Ptr<Image> = if filename.is_empty() { let image: Ptr<Image> = if filename.is_empty() {
Ptr::null() Ptr::null()
} else { } else {
@ -102,7 +102,7 @@ impl CreateLight for GoniometricLight {
}; };
scale /= spectrum_to_photometric(i); 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 { if phi_v > 0.0 {
let k_e = compute_emissive_power(&image); let k_e = compute_emissive_power(&image);

View file

@ -132,10 +132,10 @@ pub fn create(
arena: &Arena, arena: &Arena,
) -> Result<Light> { ) -> Result<Light> {
let l = parameters.get_spectrum_array("L", SpectrumType::Illuminant); let l = parameters.get_spectrum_array("L", SpectrumType::Illuminant);
let mut scale = parameters.get_one_float("scale", 1.0); let mut scale = parameters.get_one_float("scale", 1.0)?;
let portal = parameters.get_point3f_array("portal"); let portal = parameters.get_point3f_array("portal")?;
let filename = resolve_filename(&parameters.get_one_string("filename", "")); let filename = resolve_filename(&parameters.get_one_string("filename", "")?);
let e_v = parameters.get_one_float("illuminance", -1.0); let e_v = parameters.get_one_float("illuminance", -1.0)?;
let has_spectrum = !l.is_empty(); let has_spectrum = !l.is_empty();
let has_file = !filename.is_empty(); let has_file = !filename.is_empty();

View file

@ -60,15 +60,15 @@ impl CreateLight for PointLight {
SpectrumType::Illuminant, SpectrumType::Illuminant,
) )
.unwrap(); .unwrap();
let mut scale = parameters.get_one_float("scale", 1.); let mut scale = parameters.get_one_float("scale", 1.)?;
scale /= spectrum_to_photometric(l); 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. { if phi_v > 0. {
let k_e = 4. * PI; let k_e = 4. * PI;
scale *= phi_v / k_e; 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 tf = Transform::translate(from.into());
let final_render = render_from_light * tf; let final_render = render_from_light * tf;
let specific = PointLight::new(final_render, medium.into(), l, scale); let specific = PointLight::new(final_render, medium.into(), l, scale);

View file

@ -104,11 +104,11 @@ impl CreateLight for ProjectionLight {
_colorspace: Option<&RGBColorSpace>, _colorspace: Option<&RGBColorSpace>,
arena: &Arena, arena: &Arena,
) -> Result<Light> { ) -> Result<Light> {
let mut scale = parameters.get_one_float("scale", 1.); let mut scale = parameters.get_one_float("scale", 1.)?;
let power = parameters.get_one_float("power", -1.); let power = parameters.get_one_float("power", -1.)?;
let fov = parameters.get_one_float("fov", 90.); let fov = parameters.get_one_float("fov", 90.)?;
let filename = resolve_filename(&parameters.get_one_string("filename", "")); let filename = resolve_filename(&parameters.get_one_string("filename", "")?);
if filename.is_empty() { if filename.is_empty() {
return Err(anyhow!( return Err(anyhow!(
"{}: must provide filename for projection light", "{}: must provide filename for projection light",

View file

@ -1,4 +1,3 @@
// use crate::core::image::{Image, ImageIO, ImageMetadata};
use crate::core::light::{CreateLight, lookup_spectrum}; use crate::core::light::{CreateLight, lookup_spectrum};
use crate::core::spectrum::spectrum_to_photometric; use crate::core::spectrum::spectrum_to_photometric;
use crate::core::texture::FloatTexture; use crate::core::texture::FloatTexture;
@ -72,17 +71,17 @@ impl CreateLight for SpotLight {
SpectrumType::Illuminant, SpectrumType::Illuminant,
) )
.expect("No spectrum"); .expect("No spectrum");
let mut scale = parameters.get_one_float("scale", 1.); let mut scale = parameters.get_one_float("scale", 1.)?;
let coneangle = parameters.get_one_float("coneangle", 30.); let coneangle = parameters.get_one_float("coneangle", 30.)?;
let conedelta = parameters.get_one_float("conedelta", 5.); let conedelta = parameters.get_one_float("conedelta", 5.)?;
let from = parameters.get_one_point3f("from", Point3f::zero()); let from = parameters.get_one_point3f("from", Point3f::zero())?;
let to = parameters.get_one_point3f("to", Point3f::new(0., 0., 1.)); 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 dir_to_z = Transform::from(Frame::from_z((to - from).normalize()));
let t = Transform::translate(from.into()) * dir_to_z.inverse(); let t = Transform::translate(from.into()) * dir_to_z.inverse();
let final_render = render_from_light * t; let final_render = render_from_light * t;
scale /= spectrum_to_photometric(i); 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. { if phi_v > 0. {
let cos_falloff_end = radians(coneangle).cos(); let cos_falloff_end = radians(coneangle).cos();
let cos_falloff_start = radians(coneangle - conedelta).cos(); let cos_falloff_start = radians(coneangle - conedelta).cos();

View file

@ -2,7 +2,7 @@ use crate::core::image::Image;
use crate::core::material::CreateMaterial; use crate::core::material::CreateMaterial;
use crate::core::texture::SpectrumTexture; use crate::core::texture::SpectrumTexture;
use crate::spectra::data::get_named_spectrum; 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 anyhow::{Result, bail};
use shared::core::material::Material; use shared::core::material::Material;
use shared::core::spectrum::Spectrum; use shared::core::spectrum::Spectrum;

View file

@ -2,7 +2,7 @@ use crate::core::shape::{ALL_TRIANGLE_MESHES, CreateShape};
use crate::core::texture::FloatTexture; use crate::core::texture::FloatTexture;
use crate::shapes::mesh::TriangleMesh; use crate::shapes::mesh::TriangleMesh;
use crate::utils::{Arena, FileLoc, ParameterDictionary}; use crate::utils::{Arena, FileLoc, ParameterDictionary};
use anyhow::{Result, anyhow}; use anyhow::{Result, bail};
use log::warn; use log::warn;
use shared::core::shape::Shape; use shared::core::shape::Shape;
use shared::shapes::TriangleShape; use shared::shapes::TriangleShape;
@ -29,9 +29,7 @@ impl CreateShape for TriangleShape {
if vertex_indices.is_empty() { if vertex_indices.is_empty() {
if p.len() == 3 { if p.len() == 3 {
} else { } else {
return Err(anyhow!( return bail!("Vertex indices \"indices\" must be provided with triangle mesh.");
"Vertex indices \"indices\" must be provided with triangle mesh."
));
} }
} else if vertex_indices.len() % 3 != 0 { } else if vertex_indices.len() % 3 != 0 {
let excess = vertex_indices.len() % 3; let excess = vertex_indices.len() % 3;
@ -45,9 +43,7 @@ impl CreateShape for TriangleShape {
} }
if p.is_empty() { if p.is_empty() {
return Err(anyhow!( return bail!("Vertex positions \"P\" must be provided with triangle mesh.");
"Vertex positions \"P\" must be provided with triangle mesh."
));
} }
if !uvs.is_empty() && uvs.len() != p.len() { if !uvs.is_empty() && uvs.len() != p.len() {
@ -68,11 +64,11 @@ impl CreateShape for TriangleShape {
for (_, &index) in vertex_indices.iter().enumerate() { for (_, &index) in vertex_indices.iter().enumerate() {
// Check for negative indices (if keeping i32) or out of bounds // Check for negative indices (if keeping i32) or out of bounds
if index < 0 || index as usize >= p.len() { 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.", "TriangleMesh has out-of-bounds vertex index {} ({} \"P\" values were given). Discarding this mesh.",
index, index,
p.len() p.len()
)); );
} }
} }

View file

@ -12,7 +12,7 @@ pub struct SystemAllocator;
impl Default for SystemAllocator { impl Default for SystemAllocator {
fn default() -> Self { fn default() -> Self {
Self Self {}
} }
} }
@ -35,72 +35,130 @@ impl GpuAllocator for SystemAllocator {
/// CUDA unified memory backend using CudaAllocator /// CUDA unified memory backend using CudaAllocator
#[cfg(feature = "cuda")] #[cfg(feature = "cuda")]
pub struct CudaAllocator; pub mod cuda {
use super::GpuAllocator;
use std::alloc::Layout;
#[cfg(feature = "cuda")] pub struct CudaAllocator;
impl Default for CudaAllocator {
fn default() -> Self { impl Default for CudaAllocator {
Self fn default() -> Self {
Self {}
}
} }
}
#[cfg(feature = "cuda")] impl GpuAllocator for CudaAllocator {
impl GpuAllocator for CudaAllocator { unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
unsafe fn alloc(&self, layout: Layout) -> *mut u8 { use cust::memory::cuda_malloc_unified;
use cust::memory::cuda_malloc_unified; use cust_raw::driver_sys::*;
let size = layout.size().max(layout.align()); let size = layout.size().max(layout.align());
if size == 0 { if size == 0 {
return layout.align() as *mut u8; 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::<u8>(size).expect("cuda_malloc_unified failed") };
let raw = unified_ptr.as_raw_mut();
std::mem::forget(unified_ptr);
raw
} }
let mut unified_ptr = unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
unsafe { cuda_malloc_unified::<u8>(size).expect("cuda_malloc_unified failed") }; use cust::memory::{UnifiedPointer, cuda_free_unified};
let raw = unified_ptr.as_raw_mut(); if layout.size() > 0 {
std::mem::forget(unified_ptr); let _ = unsafe { cuda_free_unified(UnifiedPointer::wrap(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)) };
} }
} }
} }
/// Vulkan backend using gpu-allocator. /// Vulkan backend (gpu-allocator for now, there might be a better solution)
#[cfg(feature = "vulkan")] #[cfg(feature = "vulkan")]
pub mod vulkan { pub mod vulkan {
use super::GpuAllocator; use super::GpuAllocator;
use ash::vk; use ash::vk;
use gpu_allocator::MemoryLocation; 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 parking_lot::Mutex;
use std::alloc::Layout; use std::alloc::Layout;
use std::collections::HashMap; use std::collections::HashMap;
use std::sync::OnceLock;
pub struct VulkanAllocator { // So, having a static allocator seems like a terrible idea
allocator: Mutex<Allocator>, // But I cant find a way to get a functioning generic Arena constructor
/// Track pointer -> Allocation so we can free later. // That might not even be a necessity, since rust-gpu/rust-cuda might actually handle that
allocations: Mutex<HashMap<usize, Allocation>>, // differently
static VK_ALLOCATOR: OnceLock<VulkanAllocatorInner> = OnceLock::new();
struct VulkanAllocatorInner {
state: Mutex<VulkanState>,
} }
impl VulkanAllocator { struct VulkanState {
pub fn new(allocator: Allocator) -> Self { allocator: Allocator,
Self { allocations: HashMap<usize, Allocation>,
allocator: Mutex::new(allocator), }
allocations: Mutex::new(HashMap::new()),
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 { impl Default for VulkanAllocator {
fn default() -> Self { fn default() -> Self {
let _ = inner();
Self 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 { impl GpuAllocator for VulkanAllocator {
unsafe fn alloc(&self, layout: Layout) -> *mut u8 { unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
let size = layout.size().max(layout.align()); let size = layout.size().max(layout.align());
@ -108,8 +166,10 @@ pub mod vulkan {
return layout.align() as *mut u8; return layout.align() as *mut u8;
} }
let mut alloc = self.allocator.lock(); let inner = inner();
let allocation = alloc let mut state = inner.state.lock();
let allocation = state
.allocator
.allocate(&AllocationCreateDesc { .allocate(&AllocationCreateDesc {
name: "arena", name: "arena",
requirements: vk::MemoryRequirements { requirements: vk::MemoryRequirements {
@ -128,7 +188,7 @@ pub mod vulkan {
.expect("Vulkan allocation not host-mapped") .expect("Vulkan allocation not host-mapped")
.as_ptr() as *mut u8; .as_ptr() as *mut u8;
self.allocations.lock().insert(ptr as usize, allocation); state.allocations.insert(ptr as usize, allocation);
ptr ptr
} }
@ -136,9 +196,11 @@ pub mod vulkan {
if layout.size() == 0 { if layout.size() == 0 {
return; return;
} }
if let Some(allocation) = self.allocations.lock().remove(&(ptr as usize)) { let inner = inner();
self.allocator let mut state = inner.state.lock();
.lock() if let Some(allocation) = state.allocations.remove(&(ptr as usize)) {
state
.allocator
.free(allocation) .free(allocation)
.expect("Vulkan free failed"); .expect("Vulkan free failed");
} }

View file

@ -1,6 +1,33 @@
use half::f16;
use shared::Float; use shared::Float;
use shared::utils::hash::hash_buffer; 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::<u16, cuda_std::f16>(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 struct DigitPermutation {
pub permutations: Vec<u16>, pub permutations: Vec<u16>,
@ -68,13 +95,7 @@ pub fn compute_radical_inverse_permutations(seed: u64) -> (Vec<u16>, Vec<DeviceD
// let ptr_to_data = storage_base_ptr.add(current_offset); // let ptr_to_data = storage_base_ptr.add(current_offset);
views.push( views.push(DigitPermutation::new(base as i32, n_digits as u64).device);
DigitPermutation::new(
base as i32,
n_digits as u64,
)
.device,
);
// current_offset += len; // current_offset += len;
} }

View file

@ -24,7 +24,7 @@ pub use strings::*;
pub type Arena = arena::Arena<backend::vulkan::VulkanAllocator>; pub type Arena = arena::Arena<backend::vulkan::VulkanAllocator>;
#[cfg(all(feature = "cuda", not(feature = "vulkan")))] #[cfg(all(feature = "cuda", not(feature = "vulkan")))]
pub type Arena = arena::Arena<backend::CudaAllocator>; pub type Arena = arena::Arena<backend::cuda::CudaAllocator>;
#[cfg(not(any(feature = "cuda", feature = "vulkan")))] #[cfg(not(any(feature = "cuda", feature = "vulkan")))]
pub type Arena = arena::Arena<backend::SystemAllocator>; pub type Arena = arena::Arena<backend::SystemAllocator>;

View file

@ -1,3 +1,4 @@
use anyhow::Result;
use flate2::read::GzDecoder; use flate2::read::GzDecoder;
use memmap2::Mmap; use memmap2::Mmap;
use std::collections::HashMap; use std::collections::HashMap;
@ -12,10 +13,23 @@ use crate::utils::parameters::{ParameterDictionary, ParsedParameter, ParsedParam
use shared::Float; use shared::Float;
pub trait ParserTarget { pub trait ParserTarget {
fn identity(&mut self, loc: FileLoc); fn identity(&mut self, loc: FileLoc) -> Result<(), ParserError>;
fn translate(&mut self, dx: Float, dy: Float, dz: Float, loc: FileLoc); fn translate(
fn rotate(&mut self, angle: Float, ax: Float, ay: Float, az: Float, loc: FileLoc); &mut self,
fn scale(&mut self, sx: Float, sy: Float, sz: Float, loc: FileLoc); 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( fn look_at(
&mut self, &mut self,
ex: Float, ex: Float,
@ -28,32 +42,85 @@ pub trait ParserTarget {
uy: Float, uy: Float,
uz: Float, uz: Float,
loc: FileLoc, loc: FileLoc,
); ) -> Result<(), ParserError>;
fn transform(&mut self, transform: &[Float; 16], loc: FileLoc); fn transform(&mut self, transform: &[Float; 16], loc: FileLoc) -> Result<(), ParserError>;
fn concat_transform(&mut self, transform: &[Float; 16], loc: FileLoc); 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 option(&mut self, name: &str, value: &str, loc: FileLoc) -> Result<(), ParserError>;
fn coord_sys_transform(&mut self, name: &str, loc: FileLoc); fn color_space(&mut self, n: &str, loc: FileLoc) -> Result<(), ParserError>;
fn active_transform_all(&mut self, loc: FileLoc); fn pixel_filter(
fn active_transform_end_time(&mut self, loc: FileLoc); &mut self,
fn active_transform_start_time(&mut self, loc: FileLoc); name: &str,
fn transform_times(&mut self, start: Float, end: Float, loc: FileLoc); 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 world_begin(&mut self, loc: FileLoc, arena: Arc<Arena>) -> Result<(), ParserError>;
fn color_space(&mut self, n: &str, loc: FileLoc); fn attribute_begin(&mut self, loc: FileLoc) -> Result<(), ParserError>;
fn pixel_filter(&mut self, name: &str, params: &ParsedParameterVector, loc: FileLoc); fn attribute_end(&mut self, loc: FileLoc) -> Result<(), ParserError>;
fn film(&mut self, type_name: &str, params: &ParsedParameterVector, loc: FileLoc); fn attribute(
fn accelerator(&mut self, name: &str, params: &ParsedParameterVector, loc: FileLoc); &mut self,
fn integrator(&mut self, name: &str, params: &ParsedParameterVector, loc: FileLoc); target: &str,
fn camera(&mut self, name: &str, params: &ParsedParameterVector, loc: FileLoc); params: ParsedParameterVector,
fn make_named_medium(&mut self, name: &str, params: &ParsedParameterVector, loc: FileLoc); loc: FileLoc,
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);
fn world_begin(&mut self, loc: FileLoc, arena: Arc<Arena>);
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 texture( fn texture(
&mut self, &mut self,
@ -63,22 +130,47 @@ pub trait ParserTarget {
params: &ParsedParameterVector, params: &ParsedParameterVector,
loc: FileLoc, loc: FileLoc,
arena: Arc<Arena>, arena: Arc<Arena>,
); ) -> Result<(), ParserError>;
fn material(&mut self, name: &str, params: &ParsedParameterVector, loc: FileLoc); fn material(
fn make_named_material(&mut self, name: &str, params: &ParsedParameterVector, loc: FileLoc); &mut self,
fn named_material(&mut self, name: &str, loc: FileLoc); 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 light_source(
fn area_light_source(&mut self, name: &str, params: &ParsedParameterVector, loc: FileLoc); &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 shape(
fn reverse_orientation(&mut self, loc: FileLoc); &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_begin(&mut self, name: &str, loc: FileLoc) -> Result<(), ParserError>;
fn object_end(&mut self, loc: FileLoc); fn object_end(&mut self, loc: FileLoc) -> Result<(), ParserError>;
fn object_instance(&mut self, name: &str, loc: FileLoc); 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)] #[derive(Debug, Clone)]
@ -394,24 +486,42 @@ impl FormattingParserTarget {
} }
impl ParserTarget for 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); 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)); 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); println!("{}Translate {} {} {}", self.indent(0), dx, dy, dz);
Ok(())
} }
fn rotate(&mut self, angle: Float, ax: Float, ay: Float, az: Float, _loc: FileLoc) { fn rotate(
println!("{}Rotate {} {} {} {}", self.indent(0), angle, ax, ay, az); &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); println!("{}Scale {} {} {}", self.indent(0), sx, sy, sz);
Ok(())
} }
fn look_at( fn look_at(
@ -426,7 +536,7 @@ impl ParserTarget for FormattingParserTarget {
uy: Float, uy: Float,
uz: Float, uz: Float,
_loc: FileLoc, _loc: FileLoc,
) { ) -> Result<(), ParserError> {
println!( println!(
"{}LookAt {} {} {} {} {} {} {} {} {}", "{}LookAt {} {} {} {} {} {} {} {} {}",
self.indent(0), self.indent(0),
@ -440,56 +550,75 @@ impl ParserTarget for FormattingParserTarget {
uy, uy,
uz uz
); );
Ok(())
} }
fn concat_transform(&mut self, t: &[Float; 16], _loc: FileLoc) { fn concat_transform(&mut self, t: &[Float; 16], _loc: FileLoc) -> Result<(), ParserError> {
// Rust arrays verify size at compile time, simpler than C++ pointers
println!("{}ConcatTransform [ {:?} ]", self.indent(0), t); 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); 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); 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); println!("{}CoordSysTransform \"{}\"", self.indent(0), name);
Ok(())
} }
fn world_begin(&mut self, _loc: FileLoc, _arena: Arc<Arena>) { fn world_begin(&mut self, _loc: FileLoc, _arena: Arc<Arena>) -> Result<(), ParserError> {
println!("{}WorldBegin", self.indent(0)); println!("{}WorldBegin", self.indent(0));
self.cat_indent_count += 4; 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)); println!("{}AttributeBegin", self.indent(0));
self.cat_indent_count += 4; 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); self.cat_indent_count = self.cat_indent_count.saturating_sub(4);
println!("{}AttributeEnd", self.indent(0)); 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!( println!(
"{}Shape \"{}\" ... ({} params)", "{}Shape \"{}\" ... ({} params)",
self.indent(0), self.indent(0),
name, name,
params.len() 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!( println!(
"{}Material \"{}\" ... ({} params)", "{}Material \"{}\" ... ({} params)",
self.indent(0), self.indent(0),
name, name,
params.len() params.len()
); );
Ok(())
} }
fn texture( fn texture(
@ -500,7 +629,7 @@ impl ParserTarget for FormattingParserTarget {
_params: &ParsedParameterVector, _params: &ParsedParameterVector,
_loc: FileLoc, _loc: FileLoc,
_arena: Arc<Arena>, _arena: Arc<Arena>,
) { ) -> Result<(), ParserError> {
println!( println!(
"{}Texture \"{}\" \"{}\" \"{}\"", "{}Texture \"{}\" \"{}\" \"{}\"",
self.indent(0), self.indent(0),
@ -508,38 +637,137 @@ impl ParserTarget for FormattingParserTarget {
type_name, type_name,
tex_name tex_name
); );
Ok(())
} }
fn active_transform_all(&mut self, _loc: FileLoc) {} fn active_transform_all(&mut self, _loc: FileLoc) -> Result<(), ParserError> {
fn active_transform_end_time(&mut self, _loc: FileLoc) {} Ok(())
fn active_transform_start_time(&mut self, _loc: FileLoc) {} }
fn transform_times(&mut self, _s: Float, _e: Float, _loc: FileLoc) {} fn active_transform_end_time(&mut self, _loc: FileLoc) -> Result<(), ParserError> {
fn color_space(&mut self, _n: &str, _loc: FileLoc) {} Ok(())
fn pixel_filter(&mut self, _n: &str, _p: &ParsedParameterVector, _loc: FileLoc) {} }
fn film(&mut self, _t: &str, _p: &ParsedParameterVector, _loc: FileLoc) {} fn active_transform_start_time(&mut self, _loc: FileLoc) -> Result<(), ParserError> {
fn accelerator(&mut self, _n: &str, _p: &ParsedParameterVector, _loc: FileLoc) {} Ok(())
fn integrator(&mut self, _n: &str, _p: &ParsedParameterVector, _loc: FileLoc) {} }
fn camera(&mut self, _n: &str, _p: &ParsedParameterVector, _loc: FileLoc) {} fn transform_times(&mut self, _s: Float, _e: Float, _loc: FileLoc) -> Result<(), ParserError> {
fn make_named_medium(&mut self, _n: &str, _p: &ParsedParameterVector, _loc: FileLoc) {} Ok(())
fn medium_interface(&mut self, _i: &str, _o: &str, _loc: FileLoc) {} }
fn sampler(&mut self, _n: &str, _p: &ParsedParameterVector, _loc: FileLoc) {} fn color_space(&mut self, _n: &str, _loc: FileLoc) -> Result<(), ParserError> {
fn attribute(&mut self, _t: &str, _p: ParsedParameterVector, _loc: FileLoc) {} Ok(())
fn make_named_material(&mut self, _n: &str, _p: &ParsedParameterVector, _loc: FileLoc) {} }
fn named_material(&mut self, _n: &str, _loc: FileLoc) {} fn pixel_filter(
fn light_source(&mut self, _n: &str, _p: &ParsedParameterVector, _loc: FileLoc) {} &mut self,
fn area_light_source(&mut self, _n: &str, _p: &ParsedParameterVector, _loc: FileLoc) {} _n: &str,
fn reverse_orientation(&mut self, _loc: FileLoc) {} _p: &ParsedParameterVector,
fn object_begin(&mut self, name: &str, _loc: FileLoc) { _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); println!("{}ObjectBegin \"{}\"", self.indent(0), name);
self.cat_indent_count += 4; 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); self.cat_indent_count = self.cat_indent_count.saturating_sub(4);
println!("{}ObjectEnd", self.indent(0)); println!("{}ObjectEnd", self.indent(0));
Ok(())
} }
fn object_instance(&mut self, _n: &str, _loc: FileLoc) {} fn object_instance(&mut self, _n: &str, _loc: FileLoc) -> Result<(), ParserError> {
fn end_of_files(&mut self) { Ok(())
}
fn end_of_files(&mut self) -> Result<(), ParserError> {
self.cat_indent_count = 0; self.cat_indent_count = 0;
Ok(())
} }
} }
@ -756,17 +984,17 @@ impl<'a> SceneParser<'a> {
match first_char { match first_char {
'A' => match token.text.as_str() { 'A' => match token.text.as_str() {
"AttributeBegin" => self.target.attribute_begin(token.loc), "AttributeBegin" => self.target.attribute_begin(token.loc)?,
"AttributeEnd" => self.target.attribute_end(token.loc), "AttributeEnd" => self.target.attribute_end(token.loc)?,
"Attribute" => { "Attribute" => {
self.parse_basic_entry(|t, n, p, l| t.attribute(n, p.to_vec(), l))? self.parse_basic_entry(|t, n, p, l| t.attribute(n, p.to_vec(), l))?
} }
"ActiveTransform" => { "ActiveTransform" => {
let a = self.next_token_required()?; let a = self.next_token_required()?;
match a.text.as_str() { match a.text.as_str() {
"All" => self.target.active_transform_all(token.loc), "All" => self.target.active_transform_all(token.loc)?,
"EndTime" => self.target.active_transform_end_time(token.loc), "EndTime" => self.target.active_transform_end_time(token.loc)?,
"StartTime" => self.target.active_transform_start_time(token.loc), "StartTime" => self.target.active_transform_start_time(token.loc)?,
_ => { _ => {
return Err(ParserError::Generic( return Err(ParserError::Generic(
"Unknown ActiveTransform type".into(), "Unknown ActiveTransform type".into(),
@ -796,19 +1024,19 @@ impl<'a> SceneParser<'a> {
m[i] = self.expect_float()?; m[i] = self.expect_float()?;
} }
self.expect_token("]")?; self.expect_token("]")?;
self.target.concat_transform(&m, token.loc); self.target.concat_transform(&m, token.loc)?;
} }
"CoordinateSystem" => { "CoordinateSystem" => {
let n = self.expect_quoted_string()?; let n = self.expect_quoted_string()?;
self.target.coordinate_system(&n, token.loc); self.target.coordinate_system(&n, token.loc)?;
} }
"CoordSysTransform" => { "CoordSysTransform" => {
let n = self.expect_quoted_string()?; let n = self.expect_quoted_string()?;
self.target.coord_sys_transform(&n, token.loc); self.target.coord_sys_transform(&n, token.loc)?;
} }
"ColorSpace" => { "ColorSpace" => {
let n = self.expect_quoted_string()?; let n = self.expect_quoted_string()?;
self.target.color_space(&n, token.loc); self.target.color_space(&n, token.loc)?;
} }
_ => { _ => {
return Err(ParserError::Generic( return Err(ParserError::Generic(
@ -846,7 +1074,7 @@ impl<'a> SceneParser<'a> {
self.file_stack.push(new_tokenizer); self.file_stack.push(new_tokenizer);
} }
"Identity" => self.target.identity(token.loc), "Identity" => self.target.identity(token.loc)?,
_ => { _ => {
return Err(ParserError::Generic( return Err(ParserError::Generic(
format!("Unknown directive {}", token.text), format!("Unknown directive {}", token.text),
@ -892,7 +1120,7 @@ impl<'a> SceneParser<'a> {
self.unget(next); self.unget(next);
inside.clone() inside.clone()
}; };
self.target.medium_interface(&inside, &outside, token.loc); self.target.medium_interface(&inside, &outside, token.loc)?;
} }
_ => { _ => {
return Err(ParserError::Generic( return Err(ParserError::Generic(
@ -905,7 +1133,7 @@ impl<'a> SceneParser<'a> {
'N' => match token.text.as_str() { 'N' => match token.text.as_str() {
"NamedMaterial" => { "NamedMaterial" => {
let n = self.expect_quoted_string()?; let n = self.expect_quoted_string()?;
self.target.named_material(&n, token.loc); self.target.named_material(&n, token.loc)?;
} }
_ => { _ => {
return Err(ParserError::Generic( return Err(ParserError::Generic(
@ -918,18 +1146,18 @@ impl<'a> SceneParser<'a> {
'O' => match token.text.as_str() { 'O' => match token.text.as_str() {
"ObjectBegin" => { "ObjectBegin" => {
let n = self.expect_quoted_string()?; 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" => { "ObjectInstance" => {
let n = self.expect_quoted_string()?; let n = self.expect_quoted_string()?;
self.target.object_instance(&n, token.loc); self.target.object_instance(&n, token.loc)?;
} }
"Option" => { "Option" => {
let name = self.expect_quoted_string()?; let name = self.expect_quoted_string()?;
let val_tok = self.next_token_required()?; let val_tok = self.next_token_required()?;
let val = val_tok.dequote().to_string(); 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( return Err(ParserError::Generic(
@ -952,7 +1180,7 @@ impl<'a> SceneParser<'a> {
}, },
'R' => match token.text.as_str() { 'R' => match token.text.as_str() {
"ReverseOrientation" => self.target.reverse_orientation(token.loc), "ReverseOrientation" => self.target.reverse_orientation(token.loc)?,
"Rotate" => { "Rotate" => {
let angle = self.expect_float()?; let angle = self.expect_float()?;
let ax = self.expect_float()?; let ax = self.expect_float()?;
@ -986,8 +1214,8 @@ impl<'a> SceneParser<'a> {
}, },
'T' => match token.text.as_str() { 'T' => match token.text.as_str() {
"TransformBegin" => self.target.attribute_begin(token.loc), "TransformBegin" => self.target.attribute_begin(token.loc)?,
"TransformEnd" => self.target.attribute_end(token.loc), "TransformEnd" => self.target.attribute_end(token.loc)?,
"Transform" => { "Transform" => {
self.expect_token("[")?; self.expect_token("[")?;
let mut m = [0.0; 16]; let mut m = [0.0; 16];
@ -995,18 +1223,18 @@ impl<'a> SceneParser<'a> {
m[i] = self.expect_float()?; m[i] = self.expect_float()?;
} }
self.expect_token("]")?; self.expect_token("]")?;
self.target.transform(&m, token.loc); self.target.transform(&m, token.loc)?;
} }
"Translate" => { "Translate" => {
let x = self.expect_float()?; let x = self.expect_float()?;
let y = self.expect_float()?; let y = self.expect_float()?;
let z = 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" => { "TransformTimes" => {
let s = self.expect_float()?; let s = self.expect_float()?;
let e = 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" => { "Texture" => {
let name = self.expect_quoted_string()?; let name = self.expect_quoted_string()?;
@ -1020,7 +1248,7 @@ impl<'a> SceneParser<'a> {
&params, &params,
token.loc, token.loc,
arena.clone(), arena.clone(),
); )?;
} }
_ => { _ => {
return Err(ParserError::Generic( return Err(ParserError::Generic(
@ -1031,7 +1259,7 @@ impl<'a> SceneParser<'a> {
}, },
'W' => match token.text.as_str() { '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" => {} "WorldEnd" => {}
_ => { _ => {
return Err(ParserError::Generic( return Err(ParserError::Generic(
@ -1050,7 +1278,7 @@ impl<'a> SceneParser<'a> {
} }
} }
self.target.end_of_files(); self.target.end_of_files()?;
Ok(()) Ok(())
} }
@ -1079,7 +1307,12 @@ impl<'a> SceneParser<'a> {
fn parse_basic_entry<F>(&mut self, mut func: F) -> Result<(), ParserError> fn parse_basic_entry<F>(&mut self, mut func: F) -> Result<(), ParserError>
where 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_token = self.next_token_required()?;
let type_name = if type_token.is_quoted() { let type_name = if type_token.is_quoted() {
@ -1089,7 +1322,6 @@ impl<'a> SceneParser<'a> {
}; };
let params = self.parse_parameters()?; let params = self.parse_parameters()?;
func(self.target, &type_name, &params, type_token.loc); func(self.target, &type_name, &params, type_token.loc)
Ok(())
} }
} }