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
.vscode
rust-analyzer.json
rust-analyzer.toml
data/
src/gpu/
src/tests/

View file

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

View file

@ -1,11 +1,18 @@
[package]
name = "kernels"
version = "0.1.0"
edition = "2024"
[dependencies]
cuda_std = { git = "https://github.com/rust-gpu/rust-cuda", rev = "7fa76f3d717038a92c90bf4a482b0b8dd3259344" }
shared = { path = "../shared", features = ["cuda"] }
edition = "2021"
[lib]
crate-type = ["cdylib", "rlib"]
crate-type = ["dylib"]
[dependencies]
spirv-std = { git = "https://github.com/Rust-GPU/rust-gpu", branch = "main" }
shared = { path = "../shared" }
[package.metadata.rust-gpu.install]
spirv-builder-source = "https://github.com/Rust-GPU/rust-gpu"
[package.metadata.rust-gpu.build]
target = "spirv-unknown-vulkan1.2"

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -12,7 +12,7 @@ pub use self::primitives::{
Vector3i,
};
pub use self::ray::{Ray, RayDifferential};
pub use self::traits::{Lerp, Sqrt, Tuple, VectorLike};
pub use self::traits::{Lerp, SqrtExt, Tuple, VectorLike};
use crate::core::pbrt::{Float, PI};
use crate::utils::math::{clamp, square};

View file

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

View file

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

View file

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

View file

@ -13,7 +13,6 @@ use crate::core::material::{
Material, MaterialEvalContext, MaterialTrait, NormalBumpEvalContext, bump_map, normal_map,
};
use crate::core::medium::{Medium, MediumInterface, PhaseFunction};
use crate::core::options::get_options;
use crate::core::sampler::{Sampler, SamplerTrait};
use crate::core::shape::Shape;
use crate::core::texture::{GPUFloatTexture, UniversalTextureEvaluator};
@ -21,8 +20,6 @@ use crate::spectra::{SampledSpectrum, SampledWavelengths};
use crate::utils::Ptr;
use crate::utils::math::{clamp, difference_of_products, square};
use enum_dispatch::enum_dispatch;
use std::any::Any;
use std::default;
#[repr(C)]
#[derive(Default, Copy, Clone, Debug)]
@ -347,69 +344,6 @@ impl SurfaceInteraction {
}
}
#[cfg(not(target_os = "cuda"))]
pub fn get_bsdf(
&mut self,
r: &Ray,
lambda: &SampledWavelengths,
camera: &Camera,
sampler: &mut Sampler,
) -> Option<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(
&mut self,
tex_eval: &UniversalTextureEvaluator,

View file

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

View file

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

View file

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

View file

@ -1,9 +1,6 @@
use crate::core::geometry::Lerp;
use core::sync::atomic::{AtomicU64, Ordering as SyncOrdering};
use core::ops::{Add, Mul};
use num_traits::{Num, PrimInt};
use std::hash::Hash;
use std::ops::{Add, Mul};
use std::sync::{Arc, Mutex};
use crate::core::image::DeviceImage;
use crate::core::light::LightTrait;
@ -15,40 +12,6 @@ use crate::utils::Ptr;
pub type Float = f32;
// #[derive(Copy, Clone, Debug)]
// pub struct Host;
//
// #[derive(Copy, Clone, Debug)]
// pub struct Device;
//
// pub trait Backend: Copy + Clone + 'static {
// type ShapeRef: Copy + Clone;
// type TextureRef: Copy + Clone;
// type ImageRef: Copy + Clone;
// type DenseSpectrumRef = Ptr<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"))]
pub type FloatBits = u32;
@ -134,45 +97,50 @@ impl FloatBitOps for f64 {
pub const MACHINE_EPSILON: Float = Float::EPSILON * 0.5;
pub const SHADOW_EPSILON: Float = 0.0001;
pub const ONE_MINUS_EPSILON: Float = 0.99999994;
pub const PI: Float = std::f32::consts::PI;
pub const INV_PI: Float = 0.318_309_886_183_790_671_54;
pub const PI: Float = core::f32::consts::PI;
pub const INV_PI: Float = core::f32::consts::FRAC_1_PI;
pub const INV_2_PI: Float = 0.159_154_943_091_895_335_77;
pub const INV_4_PI: Float = 0.079_577_471_545_947_667_88;
pub const PI_OVER_2: Float = 1.570_796_326_794_896_619_23;
pub const PI_OVER_4: Float = 0.785_398_163_397_448_309_61;
pub const SQRT_2: Float = 1.414_213_562_373_095_048_80;
pub const PI_OVER_2: Float = core::f32::consts::FRAC_PI_2;
pub const PI_OVER_4: Float = core::f32::consts::FRAC_PI_4;
pub const SQRT_2: Float = core::f32::consts::SQRT_2;
#[inline]
pub fn gamma(n: i32) -> Float {
n as Float * MACHINE_EPSILON / (1. - n as Float * MACHINE_EPSILON)
}
pub static RARE_EVENT_TOTAL_CALLS: AtomicU64 = AtomicU64::new(0);
pub static RARE_EVENT_CONDITION_MET: AtomicU64 = AtomicU64::new(0);
#[cfg(feature = "cpu_debug")]
pub mod debug {
use core::sync::atomic::AtomicU64;
use core::sync::atomic::Ordering as SyncOrdering;
pub static RARE_EVENT_TOTAL_CALLS: AtomicU64 = AtomicU64::new(0);
pub static RARE_EVENT_CONDITION_MET: AtomicU64 = AtomicU64::new(0);
#[macro_export]
macro_rules! check_rare {
($frequency_threshold:expr, $condition:expr) => {
use core::sync::atomic::{AtomicU64, Ordering as SyncOrdering};
const CHECK_INTERVAL: u64 = 4096;
#[macro_export]
macro_rules! check_rare {
($frequency_threshold:expr, $condition:expr) => {
use core::sync::atomic::{AtomicU64, Ordering as SyncOrdering};
const CHECK_INTERVAL: u64 = 4096;
let total_calls = RARE_EVENT_TOTAL_CALLS.fetch_add(1, SyncOrdering::Relaxed);
let total_calls = RARE_EVENT_TOTAL_CALLS.fetch_add(1, SyncOrdering::Relaxed);
if $condition {
RARE_EVENT_CONDITION_MET.fetch_add(1, SyncOrdering::Relaxed);
}
if $condition {
RARE_EVENT_CONDITION_MET.fetch_add(1, SyncOrdering::Relaxed);
}
if (total_calls + 1) % CHECK_INTERVAL == 0 {
let met_count = RARE_EVENT_CONDITION_MET.load(SyncOrdering::Relaxed);
if met_count > 0 {
let frequency = met_count as f64 / (total_calls + 1) as f64;
if frequency > $frequency_threshold {
panic!(
"Rare event occurred with frequency {} which is > threshold {}",
frequency, $frequency_threshold
);
if (total_calls + 1) % CHECK_INTERVAL == 0 {
let met_count = RARE_EVENT_CONDITION_MET.load(SyncOrdering::Relaxed);
if met_count > 0 {
let frequency = met_count as f64 / (total_calls + 1) as f64;
if frequency > $frequency_threshold {
panic!(
"Rare event occurred with frequency {} which is > threshold {}",
frequency, $frequency_threshold
);
}
}
}
}
};
};
}
}

View file

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

View file

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

View file

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

View file

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

View file

@ -1,6 +1,5 @@
use crate::Float;
use bytemuck::cast_slice;
use once_cell::sync::Lazy;
#[repr(C, align(16))]
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::geometry::{Point2f, Vector2f};
use crate::utils::math::{lerp, windowed_sinc};
use rand::Rng;
#[repr(C)]
#[derive(Clone, Debug, Copy)]
@ -10,6 +9,7 @@ pub struct LanczosSincFilter {
pub radius: Vector2f,
pub tau: Float,
pub sampler: FilterSampler,
pub integral: Float,
}
impl FilterTrait for LanczosSincFilter {
@ -23,26 +23,7 @@ impl FilterTrait for LanczosSincFilter {
}
fn integral(&self) -> Float {
let sqrt_samples = 64;
let n_samples = sqrt_samples * sqrt_samples;
let area = (2.0 * self.radius.x()) * (2.0 * self.radius.y());
let mut sum = 0.0;
let mut rng = rand::rng();
for y in 0..sqrt_samples {
for x in 0..sqrt_samples {
let u = Point2f::new(
(x as Float + rng.random::<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
self.integral
}
fn sample(&self, u: Point2f) -> FilterSample {

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -3,7 +3,7 @@ use crate::core::geometry::{
Bounds3f, DirectionCone, Normal, Normal3f, Point2f, Point3f, Point3fi, Ray, Vector2f, Vector3,
Vector3f,
};
use crate::core::geometry::{Sqrt, Tuple, VectorLike, spherical_triangle_area};
use crate::core::geometry::{SqrtExt, Tuple, VectorLike, spherical_triangle_area};
use crate::core::interaction::{
Interaction, InteractionBase, InteractionTrait, SimpleInteraction, SurfaceInteraction,
};

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

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::utils::Ptr;
use crate::utils::math::lerp;
use std::collections::HashSet;
use std::collections::hash_map::RandomState;
use std::hash::{BuildHasher, Hash, Hasher};
use std::ops::{Add, Index, IndexMut, Mul, Sub};
use std::sync::{Arc, Mutex, RwLock};
use core::ops::{Add, Index, IndexMut, Mul, Sub};
pub trait Interpolatable:
Copy + Default + Add<Output = Self> + Sub<Output = Self> + Mul<Float, Output = Self>
@ -158,7 +154,7 @@ impl<T> SampledGrid<T> {
}
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 {

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

View file

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

View file

@ -1,4 +1,3 @@
use super::error::{InversionError, LlsError};
use crate::core::color::{RGB, XYZ};
use crate::core::geometry::{Lerp, MulAdd, Point, Point2f, Point2i, Vector, Vector3f, VectorLike};
use crate::core::pbrt::{Float, FloatBitOps, FloatBits, ONE_MINUS_EPSILON, PI, PI_OVER_4};
@ -6,13 +5,11 @@ use crate::utils::hash::{hash_buffer, mix_bits};
use crate::utils::sobol::{SOBOL_MATRICES_32, VDC_SOBOL_MATRICES, VDC_SOBOL_MATRICES_INV};
use crate::utils::Ptr;
use half::f16;
use core::fmt::{self, Display, Write};
use core::iter::{Product, Sum};
use core::mem;
use core::ops::{Add, Div, Index, IndexMut, Mul, Neg, Rem};
use num_traits::{Float as NumFloat, Num, One, Signed, Zero};
use std::error::Error;
use std::fmt::{self, Display};
use std::iter::{Product, Sum};
use std::mem;
use std::ops::{Add, Div, Index, IndexMut, Mul, Neg, Rem};
#[inline]
pub fn degrees(a: Float) -> Float {
@ -69,7 +66,7 @@ pub fn evaluate_polynomial(t: Float, coeffs: &[Float]) -> Float {
assert!(!coeffs.is_empty());
let mut result = coeffs[0];
for &c in &coeffs[1..] {
result = t.mul_add(result, c);
result = num_traits::Float::mul_add(t, result, c);
}
result
}
@ -274,13 +271,13 @@ pub fn smooth_step(x: Float, a: Float, b: Float) -> Float {
pub fn linear_least_squares<const R: usize, const N: usize>(
a: [[Float; R]; N],
b: [[Float; R]; N],
) -> Result<SquareMatrix<Float, R>, Box<dyn Error>> {
) -> Option<SquareMatrix<Float, R>> {
let am = Matrix::from(a);
let bm = Matrix::from(b);
let ata = am.transpose() * am;
let atb = am.transpose() * bm;
let at_ai = ata.inverse()?;
Ok((at_ai * atb).transpose())
Some((at_ai * atb).transpose())
}
pub fn newton_bisection<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()
}
pub fn erf(x: Float) -> Float {
let a1 = 0.254829592;
let a2 = -0.284496736;
let a3 = 1.421413741;
let a4 = -1.453152027;
let a5 = 1.061405429;
let p = 0.3275911;
let sign = if x < 0.0 { -1.0 } else { 1.0 };
let x = x.abs();
let t = 1.0 / (1.0 + p * x);
let y = 1.0 - (((((a5 * t + a4) * t) + a3) * t + a2) * t + a1) * t * (-x * x).exp();
sign * y
}
pub fn gaussian_integral(x0: Float, x1: Float, mu: Float, sigma: Float) -> Float {
assert!(sigma > 0.);
let sigma_root2 = sigma * 1.414213562373095;
0.5 * (((mu - x0) / sigma_root2).erf() - ((mu - x1) / sigma_root2).erf())
0.5 * (erf((mu - x0) / sigma_root2) - erf((mu - x1) / sigma_root2))
}
pub fn sample_linear(u: Float, a: Float, b: Float) -> Float {
@ -1079,7 +1090,7 @@ impl<T, const R: usize, const C: usize> Matrix<T, R, C> {
where
T: Clone + Zero,
{
let m: [[T; C]; R] = std::array::from_fn(|_| std::array::from_fn(|_| T::zero()));
let m: [[T; C]; R] = core::array::from_fn(|_| core::array::from_fn(|_| T::zero()));
Self { m }
}
@ -1189,21 +1200,30 @@ where
}
}
struct CountingFmt(usize);
impl Write for CountingFmt {
fn write_str(&mut self, s: &str) -> fmt::Result {
self.0 += s.len();
Ok(())
}
}
impl<T: Display, const R: usize, const C: usize> Display for Matrix<T, R, C> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut col_widths = [0; C];
// Measure without allocation
for row in self.m.iter() {
for (j, element) in row.iter().enumerate() {
let width = format!("{}", element).len();
if width > col_widths[j] {
col_widths[j] = width;
}
let mut counter = CountingFmt(0);
let _ = write!(counter, "{}", element); // Ignores errors, just counts
col_widths[j] = col_widths[j].max(counter.0);
}
}
for i in 0..R {
write!(f, "[")?;
#[allow(clippy::needless_range_loop)]
for j in 0..C {
write!(f, "{: >width$} ", self.m[i][j], width = col_widths[j])?;
}
@ -1238,8 +1258,8 @@ impl<T, const N: usize> SquareMatrix<T, N> {
T: Copy + Zero + One,
{
Self {
m: std::array::from_fn(|i| {
std::array::from_fn(|j| if i == j { T::one() } else { T::zero() })
m: core::array::from_fn(|i| {
core::array::from_fn(|j| if i == j { T::one() } else { T::zero() })
}),
}
}
@ -1302,9 +1322,9 @@ impl<T, const N: usize> SquareMatrix<T, N>
where
T: NumFloat + Sum + Product + Copy,
{
pub fn inverse(&self) -> Result<Self, InversionError> {
pub fn inverse(&self) -> Option<Self> {
if N == 0 {
return Err(InversionError::EmptyMatrix);
return None;
}
let mut mat = self.m;
@ -1322,7 +1342,7 @@ where
let pivot = mat[i][i];
if pivot.is_zero() {
return Err(InversionError::SingularMatrix);
return None;
}
let inv_pivot = T::one() / pivot;
@ -1344,7 +1364,8 @@ where
}
}
}
Ok(inv)
Some(inv)
}
pub fn determinant(&self) -> T {
@ -1448,7 +1469,8 @@ where
{
type Output = Vector<T, N>;
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)
}
}
@ -1459,7 +1481,8 @@ where
{
type Output = Point<T, N>;
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)
}
}
@ -1540,31 +1563,7 @@ mod tests {
}
}
#[inline(always)]
pub fn f16_to_f32(bits: u16) -> f32 {
#[cfg(target_os = "cuda")]
{
// Use hardware intrinsic on CUDA
// Cast bits to cuda_std::f16, then cast to f32
let half_val = unsafe { core::mem::transmute::<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 {
pub fn f16_to_f32_software(h: u16) -> f32 {
let sign = ((h >> 15) & 1) as u32;
let exp = ((h >> 10) & 0x1F) as u32;
let mant = (h & 0x3FF) as u32;

View file

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

View file

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

View file

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

View file

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

View file

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

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

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

View file

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

View file

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

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 filter;
pub mod image;
pub mod interaction;
pub mod light;
pub mod material;
pub mod medium;

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -15,10 +15,32 @@ impl LanczosFilterCreator for LanczosSincFilter {
windowed_sinc(p.x(), radius.x(), tau) * windowed_sinc(p.y(), radius.y(), tau)
});
let sqrt_samples = 64;
let n_samples = sqrt_samples * sqrt_samples;
let area = (2.0 * self.radius.x()) * (2.0 * self.radius.y());
let mut sum = 0.0;
let mut rng = rand::rng();
for y in 0..sqrt_samples {
for x in 0..sqrt_samples {
let u = Point2f::new(
(x as Float + rng.random::<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 {
radius,
tau,
sampler,
integral,
}
}
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -104,11 +104,11 @@ impl CreateLight for ProjectionLight {
_colorspace: Option<&RGBColorSpace>,
arena: &Arena,
) -> Result<Light> {
let mut scale = parameters.get_one_float("scale", 1.);
let power = parameters.get_one_float("power", -1.);
let fov = parameters.get_one_float("fov", 90.);
let mut scale = parameters.get_one_float("scale", 1.)?;
let power = parameters.get_one_float("power", -1.)?;
let fov = parameters.get_one_float("fov", 90.)?;
let filename = resolve_filename(&parameters.get_one_string("filename", ""));
let filename = resolve_filename(&parameters.get_one_string("filename", "")?);
if filename.is_empty() {
return Err(anyhow!(
"{}: 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::spectrum::spectrum_to_photometric;
use crate::core::texture::FloatTexture;
@ -72,17 +71,17 @@ impl CreateLight for SpotLight {
SpectrumType::Illuminant,
)
.expect("No spectrum");
let mut scale = parameters.get_one_float("scale", 1.);
let coneangle = parameters.get_one_float("coneangle", 30.);
let conedelta = parameters.get_one_float("conedelta", 5.);
let from = parameters.get_one_point3f("from", Point3f::zero());
let to = parameters.get_one_point3f("to", Point3f::new(0., 0., 1.));
let mut scale = parameters.get_one_float("scale", 1.)?;
let coneangle = parameters.get_one_float("coneangle", 30.)?;
let conedelta = parameters.get_one_float("conedelta", 5.)?;
let from = parameters.get_one_point3f("from", Point3f::zero())?;
let to = parameters.get_one_point3f("to", Point3f::new(0., 0., 1.))?;
let dir_to_z = Transform::from(Frame::from_z((to - from).normalize()));
let t = Transform::translate(from.into()) * dir_to_z.inverse();
let final_render = render_from_light * t;
scale /= spectrum_to_photometric(i);
let phi_v = parameters.get_one_float("power", -1.);
let phi_v = parameters.get_one_float("power", -1.)?;
if phi_v > 0. {
let cos_falloff_end = radians(coneangle).cos();
let cos_falloff_start = radians(coneangle - conedelta).cos();

View file

@ -2,7 +2,7 @@ use crate::core::image::Image;
use crate::core::material::CreateMaterial;
use crate::core::texture::SpectrumTexture;
use crate::spectra::data::get_named_spectrum;
use crate::utils::{Arena, FileLoc, TextureParameterDictionary, Upload, parameters::error_exit};
use crate::utils::{Arena, FileLoc, TextureParameterDictionary, Upload};
use anyhow::{Result, bail};
use shared::core::material::Material;
use shared::core::spectrum::Spectrum;

View file

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

View file

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

View file

@ -1,6 +1,33 @@
use half::f16;
use shared::Float;
use shared::utils::hash::hash_buffer;
use shared::utils::math::{DeviceDigitPermutation, PRIMES, permutation_element};
use shared::utils::math::{
DeviceDigitPermutation, PRIMES, f16_to_f32_software, permutation_element,
};
#[inline(always)]
pub fn f16_to_f32(bits: u16) -> f32 {
#[cfg(target_os = "cuda")]
{
// Use hardware intrinsic on CUDA
// Cast bits to cuda_f16, then cast to f32
let half_val = unsafe { core::mem::transmute::<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 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);
views.push(
DigitPermutation::new(
base as i32,
n_digits as u64,
)
.device,
);
views.push(DigitPermutation::new(base as i32, n_digits as u64).device);
// current_offset += len;
}

View file

@ -24,7 +24,7 @@ pub use strings::*;
pub type Arena = arena::Arena<backend::vulkan::VulkanAllocator>;
#[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")))]
pub type Arena = arena::Arena<backend::SystemAllocator>;

View file

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