From 226ff888741bb4c507c105ebd3bdd0f1af0fbd23 Mon Sep 17 00:00:00 2001 From: Wito Wiala Date: Thu, 21 May 2026 23:47:37 +0100 Subject: [PATCH] Fixing rendering issues, unifying rendering pipeline --- .gitignore | 1 + shared/Cargo.toml | 1 + shared/src/core/aggregates.rs | 180 ++++---- shared/src/core/bssrdf.rs | 50 ++- shared/src/lights/diffuse.rs | 2 +- shared/src/materials/complex.rs | 2 +- shared/src/utils/transform.rs | 55 ++- src/core/aggregates.rs | 701 ++++++++++++-------------------- src/core/bssrdf.rs | 40 -- src/core/film.rs | 6 +- src/core/mod.rs | 3 +- src/core/render.rs | 20 + src/core/scene/builder.rs | 24 +- src/core/scene/scene.rs | 61 ++- src/films/gbuffer.rs | 3 +- src/films/rgb.rs | 3 +- src/films/spectral.rs | 3 +- src/integrators/base.rs | 19 +- src/integrators/mod.rs | 41 +- src/integrators/path.rs | 2 +- src/integrators/pipeline.rs | 16 +- src/lights/distant.rs | 10 +- src/lights/infinite.rs | 2 +- src/lights/sampler.rs | 1 - 24 files changed, 614 insertions(+), 632 deletions(-) delete mode 100644 src/core/bssrdf.rs create mode 100644 src/core/render.rs diff --git a/.gitignore b/.gitignore index 6c3e701..b6ecf47 100644 --- a/.gitignore +++ b/.gitignore @@ -14,3 +14,4 @@ tests/ *.txt scenes/ compile.sh +output/ diff --git a/shared/Cargo.toml b/shared/Cargo.toml index 1bd5c49..6a429ac 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -4,6 +4,7 @@ version = "0.1.0" edition = "2024" [dependencies] +anyhow = "1.0.100" bitflags = "2.10.0" half = "2.7.1" bytemuck = { version = "1.24.0", features = ["derive"] } diff --git a/shared/src/core/aggregates.rs b/shared/src/core/aggregates.rs index 441f3d1..eb49849 100644 --- a/shared/src/core/aggregates.rs +++ b/shared/src/core/aggregates.rs @@ -1,7 +1,16 @@ -use crate::core::geometry::{Bounds3f, Ray, Vector3f}; +use crate::core::geometry::{Bounds3f, Point3f, Ray, Vector3f}; use crate::core::primitive::{Primitive, PrimitiveTrait}; use crate::core::shape::ShapeIntersection; -use crate::{Float, Ptr, GVec, gvec}; +use crate::{gvec, Float, GVec, Ptr}; + +#[repr(C)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum SplitMethod { + SAH, + Hlbvh, + Middle, + EqualCounts, +} #[repr(C)] #[derive(Default, Debug, Clone, Copy)] @@ -18,7 +27,7 @@ pub struct LinearBVHNode { pub struct BVHAggregate { pub node_count: u32, pub max_prims_in_node: u32, - pub primitive_count: u32, + pub split_method: SplitMethod, pub primitives: GVec, pub nodes: GVec, } @@ -26,11 +35,11 @@ pub struct BVHAggregate { impl BVHAggregate { pub fn empty() -> Self { Self { - max_prims_in_node: 0, - primitives: gvec(), - primitive_count: 0, - nodes: gvec(), node_count: 0, + max_prims_in_node: 0, + split_method: SplitMethod::SAH, + primitives: gvec(), + nodes: gvec(), } } @@ -59,9 +68,10 @@ impl PrimitiveTrait for BVHAggregate { return None; } - let mut hit_t = t_max.unwrap_or(Float::INFINITY); let mut best_si: Option = None; + let mut hit_t = t_max.unwrap_or(Float::INFINITY); + let inv_dir = Vector3f::new(1.0 / r.d.x(), 1.0 / r.d.y(), 1.0 / r.d.z()); let dir_is_neg = [ if inv_dir.x() < 0.0 { 1 } else { 0 }, @@ -69,53 +79,65 @@ impl PrimitiveTrait for BVHAggregate { if inv_dir.z() < 0.0 { 1 } else { 0 }, ]; - let mut stack = [0usize; 64]; - let mut stack_ptr = 0; - let mut node_idx = 0usize; + let mut to_visit_offset = 0; + let mut current_node_index = 0; + let mut nodes_to_visit = [0usize; 64]; loop { - let node = self.node(node_idx); + let node = &self.nodes[current_node_index]; + // Check ray against BVH node bounds using the current closest hit_t if node .bounds .intersect_p(r.o, hit_t, inv_dir, &dir_is_neg) - .is_none() + .is_some() { - if stack_ptr == 0 { - break; - } - stack_ptr -= 1; - node_idx = stack[stack_ptr]; - continue; - } + if node.n_primitives > 0 { + // Intersect ray with all primitives in this leaf + for i in 0..node.n_primitives { + let prim_idx = node.primitives_offset + i as usize; + let prim = &self.primitives[prim_idx]; - if node.n_primitives > 0 { - // Leaf: test all primitives - for i in 0..node.n_primitives { - let prim_idx = node.primitives_offset + i as usize; - let prim = self.primitive(prim_idx); - if let Some(si) = prim.intersect(r, Some(hit_t)) { - hit_t = si.t_hit(); - best_si = Some(si); + if let Some(si) = prim.intersect(r, Some(hit_t)) { + hit_t = si.t_hit(); + best_si = Some(si); + } + } + + if to_visit_offset == 0 { + break; + } + to_visit_offset -= 1; + current_node_index = nodes_to_visit[to_visit_offset]; + } else { + // Check the sign of the ray direction against the split axis + if dir_is_neg[node.axis as usize] == 1 { + // Ray is negative (Right -> Left). + // Near child is Second Child (stored in primitives_offset). + // Far child is First Child (current + 1). + + // Push Far + nodes_to_visit[to_visit_offset] = current_node_index + 1; + to_visit_offset += 1; + + // Visit Near immediately + current_node_index = node.primitives_offset; + } else { + // Ray is positive (Left -> Right). + // Push Far + nodes_to_visit[to_visit_offset] = node.primitives_offset; + to_visit_offset += 1; + + current_node_index += 1; } } - - if stack_ptr == 0 { + } else { + // The ray missed the AABB of this node. Pop stack to try the next node. + if to_visit_offset == 0 { break; } - stack_ptr -= 1; - node_idx = stack[stack_ptr]; - } else { - // Interior: push far, visit near - if dir_is_neg[node.axis as usize] == 1 { - stack[stack_ptr] = node_idx + 1; - stack_ptr += 1; - node_idx = node.primitives_offset; // second child - } else { - stack[stack_ptr] = node.primitives_offset; - stack_ptr += 1; - node_idx += 1; // first child - } + to_visit_offset -= 1; + current_node_index = nodes_to_visit[to_visit_offset]; } } @@ -123,7 +145,7 @@ impl PrimitiveTrait for BVHAggregate { } fn intersect_p(&self, r: &Ray, t_max: Option) -> bool { - if self.nodes.is_empty() || self.node_count == 0 { + if self.nodes.is_empty() { return false; } @@ -136,53 +158,57 @@ impl PrimitiveTrait for BVHAggregate { if inv_dir.z() < 0.0 { 1 } else { 0 }, ]; - let mut stack = [0usize; 64]; - let mut stack_ptr = 0; - let mut node_idx = 0usize; + let mut to_visit_offset = 0; + let mut current_node_index = 0; + let mut nodes_to_visit = [0usize; 64]; loop { - let node = self.node(node_idx); + let node = &self.nodes[current_node_index]; + // Check AABB if node .bounds .intersect_p(r.o, t_max, inv_dir, &dir_is_neg) - .is_none() + .is_some() { - if stack_ptr == 0 { - break; - } - stack_ptr -= 1; - node_idx = stack[stack_ptr]; - continue; - } + if node.n_primitives > 0 { + for i in 0..node.n_primitives { + let prim_idx = node.primitives_offset + i as usize; + let prim = &self.primitives[prim_idx]; - if node.n_primitives > 0 { - for i in 0..node.n_primitives { - let prim_idx = node.primitives_offset + i as usize; - let prim = self.primitive(prim_idx); - if prim.intersect_p(r, Some(t_max)) { - return true; + if prim.intersect_p(r, Some(t_max)) { + return true; + } + } + + // No intersection in this leaf, try next node in stack + if to_visit_offset == 0 { + break; + } + to_visit_offset -= 1; + current_node_index = nodes_to_visit[to_visit_offset]; + } else { + // Standard front-to-back traversal order helps find an occlusion + // closer to the origin faster, potentially saving work. + + if dir_is_neg[node.axis as usize] == 1 { + nodes_to_visit[to_visit_offset] = current_node_index + 1; + to_visit_offset += 1; + current_node_index = node.primitives_offset; + } else { + nodes_to_visit[to_visit_offset] = node.primitives_offset; + to_visit_offset += 1; + current_node_index += 1; } } - - if stack_ptr == 0 { + } else { + if to_visit_offset == 0 { break; } - stack_ptr -= 1; - node_idx = stack[stack_ptr]; - } else { - if dir_is_neg[node.axis as usize] == 1 { - stack[stack_ptr] = node_idx + 1; - stack_ptr += 1; - node_idx = node.primitives_offset; - } else { - stack[stack_ptr] = node.primitives_offset; - stack_ptr += 1; - node_idx += 1; - } + to_visit_offset -= 1; + current_node_index = nodes_to_visit[to_visit_offset]; } } - false } } diff --git a/shared/src/core/bssrdf.rs b/shared/src/core/bssrdf.rs index c977d9c..6559eac 100644 --- a/shared/src/core/bssrdf.rs +++ b/shared/src/core/bssrdf.rs @@ -3,11 +3,11 @@ use crate::core::bsdf::BSDF; use crate::core::geometry::{Frame, Normal3f, Point2f, Point3f, Point3fi, Vector3f}; use crate::core::interaction::{InteractionBase, ShadingGeom, SurfaceInteraction}; use crate::core::shape::Shape; -use crate::spectra::{N_SPECTRUM_SAMPLES, SampledSpectrum}; -use crate::utils::Ptr; +use crate::spectra::{SampledSpectrum, N_SPECTRUM_SAMPLES}; use crate::utils::math::{catmull_rom_weights, square}; use crate::utils::sampling::sample_catmull_rom_2d; -use crate::{Float, PI}; +use crate::utils::Ptr; +use crate::{gvec_with_capacity, Float, GVec, PI}; use enum_dispatch::enum_dispatch; use num_traits::Float as NumFloat; @@ -92,41 +92,59 @@ impl From<&SubsurfaceInteraction> for SurfaceInteraction { } #[repr(C)] -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Debug)] pub struct BSSRDFTable { pub n_rho: u32, pub n_radius: u32, - pub rho_samples: Ptr, - pub radius_samples: Ptr, - pub profile: Ptr, - pub rho_eff: Ptr, - pub profile_cdf: Ptr, + pub rho_samples: GVec, + pub radius_samples: GVec, + pub profile: GVec, + pub rho_eff: GVec, + pub profile_cdf: GVec, } impl BSSRDFTable { + pub fn new(n_rho: usize, n_radius: usize) -> Self { + let rho_samples: GVec = gvec_with_capacity(n_rho); + let radius_samples: GVec = gvec_with_capacity(n_radius); + let profile: GVec = gvec_with_capacity(n_radius * n_rho); + let rho_eff: GVec = gvec_with_capacity(n_rho); + let profile_cdf: GVec = gvec_with_capacity(n_radius * n_rho); + Self { + n_rho: n_rho.try_into().unwrap(), + n_radius: n_radius.try_into().unwrap(), + rho_samples, + radius_samples, + profile, + rho_eff, + profile_cdf, + + } + } + pub fn get_rho(&self) -> &[Float] { - unsafe { core::slice::from_raw_parts(self.rho_samples.as_ref(), self.n_rho as usize) } + &self.rho_samples } pub fn get_radius(&self) -> &[Float] { - unsafe { core::slice::from_raw_parts(self.radius_samples.as_ref(), self.n_radius as usize) } + &self.radius_samples } pub fn get_profile(&self) -> &[Float] { - let n_profile = (self.n_rho * self.n_radius) as usize; - unsafe { core::slice::from_raw_parts(self.profile.as_ref(), n_profile) } + // let n_profile = (self.n_rho * self.n_radius) as usize; + &self.profile } pub fn get_cdf(&self) -> &[Float] { - let n_profile = (self.n_rho * self.n_radius) as usize; - unsafe { core::slice::from_raw_parts(self.profile_cdf.as_ref(), n_profile) } + // let n_profile = (self.n_rho * self.n_radius) as usize; + &self.profile_cdf } pub fn eval_profile(&self, rho_index: u32, radius_index: u32) -> Float { debug_assert!(rho_index < self.n_rho); debug_assert!(radius_index < self.n_radius); let idx = (rho_index * self.n_radius + radius_index) as usize; - unsafe { *self.profile.add(idx) } + unsafe { *self.profile.as_ptr().add(idx) } } } diff --git a/shared/src/lights/diffuse.rs b/shared/src/lights/diffuse.rs index 9b47e93..d7c4b7a 100644 --- a/shared/src/lights/diffuse.rs +++ b/shared/src/lights/diffuse.rs @@ -164,7 +164,7 @@ impl LightTrait for DiffuseAreaLight { #[cfg(not(target_os = "cuda"))] fn preprocess(&mut self, _scene_bounds: &Bounds3f) { - unimplemented!() + return } #[cfg(not(target_os = "cuda"))] diff --git a/shared/src/materials/complex.rs b/shared/src/materials/complex.rs index 3f909ae..1087d2f 100644 --- a/shared/src/materials/complex.rs +++ b/shared/src/materials/complex.rs @@ -149,7 +149,7 @@ pub struct SubsurfaceMaterial { pub u_roughness: Ptr, pub v_roughness: Ptr, pub remap_roughness: bool, - pub table: BSSRDFTable, + pub table: Ptr, } impl MaterialTrait for SubsurfaceMaterial { diff --git a/shared/src/utils/transform.rs b/shared/src/utils/transform.rs index dd86b9f..1a0c153 100644 --- a/shared/src/utils/transform.rs +++ b/shared/src/utils/transform.rs @@ -2,7 +2,7 @@ use core::iter::{Product, Sum}; use core::ops::{Add, Div, Index, IndexMut, Mul}; use num_traits::Float as NumFloat; -use super::math::{SquareMatrix, radians, safe_acos}; +use super::math::{radians, safe_acos, SquareMatrix}; use super::quaternion::Quaternion; use crate::core::color::{RGB, XYZ}; use crate::core::geometry::{ @@ -12,8 +12,9 @@ use crate::core::geometry::{ use crate::core::interaction::{ Interaction, InteractionBase, InteractionTrait, MediumInteraction, SurfaceInteraction, }; +use anyhow::{bail, Context, Result}; use crate::utils::gpu_array_from_fn; -use crate::{Float, gamma}; +use crate::{gamma, Float}; #[repr(C)] #[derive(Debug, Copy, Clone)] @@ -173,27 +174,27 @@ impl TransformGeneric { let x = p.x(); let y = p.y(); let z = p.z(); - let xp = self.m[0][0] * x + self.m[0][1] * y + self.m[0][2] * z; - let yp = self.m[1][0] * x + self.m[1][1] * y + self.m[1][2] * z; - let zp = self.m[2][0] * x + self.m[2][1] * y + self.m[2][2] * z; - let wp = self.m[3][0] * x + self.m[3][1] * y + self.m[3][2] * z; + let xp = self.m[0][0] * x + self.m[0][1] * y + self.m[0][2] * z + self.m[0][3]; + let yp = self.m[1][0] * x + self.m[1][1] * y + self.m[1][2] * z + self.m[1][3]; + let zp = self.m[2][0] * x + self.m[2][1] * y + self.m[2][2] * z + self.m[2][3]; + let wp = self.m[3][0] * x + self.m[3][1] * y + self.m[3][2] * z + self.m[3][3]; let mut p_error = Vector3f::default(); if pi.is_exact() { p_error[0] = gamma(3) * ((self.m[0][0] * x).abs() + (self.m[0][1] * y).abs() - + (self.m[0][2] + z).abs() + + (self.m[0][2] * z).abs() + self.m[0][3].abs()); p_error[1] = gamma(3) * ((self.m[1][0] * x).abs() + (self.m[1][1] * y).abs() - + (self.m[1][2] + z).abs() + + (self.m[1][2] * z).abs() + self.m[1][3].abs()); p_error[2] = gamma(3) * ((self.m[2][0] * x).abs() + (self.m[2][1] * y).abs() - + (self.m[2][2] + z).abs() + + (self.m[2][2] * z).abs() + self.m[2][3].abs()); } @@ -210,7 +211,6 @@ impl TransformGeneric { let y = v.y(); let z = v.z(); - // Transform the midpoint of the vector interval let xp = self.m[0][0] * x + self.m[0][1] * y + self.m[0][2] * z; let yp = self.m[1][0] * x + self.m[1][1] * y + self.m[1][2] * z; let zp = self.m[2][0] * x + self.m[2][1] * y + self.m[2][2] * z; @@ -218,7 +218,6 @@ impl TransformGeneric { let mut v_error = Vector3f::default(); - // Propagate the error, ignoring the translational part of the matrix if vi.is_exact() { v_error[0] = gamma(3) * ((self.m[0][0] * x).abs() + (self.m[0][1] * y).abs() + (self.m[0][2] * z).abs()); @@ -226,6 +225,32 @@ impl TransformGeneric { * ((self.m[1][0] * x).abs() + (self.m[1][1] * y).abs() + (self.m[1][2] * z).abs()); v_error[2] = gamma(3) * ((self.m[2][0] * x).abs() + (self.m[2][1] * y).abs() + (self.m[2][2] * z).abs()); + } else { + let vin_error = vi.error(); + v_error[0] = (gamma(3) + 1.) + * ((self.m[0][0] * x).abs() * vin_error.x() + + (self.m[0][1] * y).abs() * vin_error.y() + + (self.m[0][2] * z).abs() * vin_error.z()) + + gamma(3) + * ((self.m[0][0] * x).abs() + + (self.m[0][1] * y).abs() + + (self.m[0][2] * z).abs()); + v_error[1] = (gamma(3) + 1.) + * ((self.m[1][0] * x).abs() * vin_error.x() + + (self.m[1][1] * y).abs() * vin_error.y() + + (self.m[1][2] * z).abs() * vin_error.z()) + + gamma(3) + * ((self.m[1][0] * x).abs() + + (self.m[1][1] * y).abs() + + (self.m[1][2] * z).abs()); + v_error[2] = (gamma(3) + 1.) + * ((self.m[2][0] * x).abs() * vin_error.x() + + (self.m[2][1] * y).abs() * vin_error.y() + + (self.m[2][2] * z).abs() * vin_error.z()) + + gamma(3) + * ((self.m[2][0] * x).abs() + + (self.m[2][1] * y).abs() + + (self.m[2][2] * z).abs()); } if wp == 1. { @@ -2094,7 +2119,7 @@ pub fn look_at( pos: impl Into, look: impl Into, up: impl Into, -) -> Option> { +) -> Result> { let mut world_from_camera: SquareMatrix = SquareMatrix::default(); // Initialize fourth column of viewing matrix let pos: Point3f = pos.into(); @@ -2108,7 +2133,7 @@ pub fn look_at( // Initialize first three columns of viewing matrix let dir = (look - pos).normalize(); if Vector3f::from(up).normalize().cross(dir).norm() == 0. { - panic!( + bail!( "LookAt: \"up\" vector ({}, {}, {}) and viewing direction ({}, {}, {}) passed to LookAt are pointing in the same direction.", up.x(), up.y(), @@ -2133,6 +2158,6 @@ pub fn look_at( world_from_camera[2][2] = dir.z(); world_from_camera[3][2] = 0.; - let camera_from_world = world_from_camera.inverse()?; - Some(TransformGeneric::new(camera_from_world, world_from_camera)) + let camera_from_world = world_from_camera.inverse().context("Failed to inverse viewing matrix")?; + Ok(TransformGeneric::new(camera_from_world, world_from_camera)) } diff --git a/src/core/aggregates.rs b/src/core/aggregates.rs index c9dc661..9eaa911 100644 --- a/src/core/aggregates.rs +++ b/src/core/aggregates.rs @@ -1,24 +1,15 @@ use crate::Arena; use rayon::prelude::*; -use shared::core::aggregates::{BVHAggregate as DeviceBVHAggregate, LinearBVHNode}; +use shared::core::aggregates::{BVHAggregate, LinearBVHNode, SplitMethod}; use shared::core::geometry::{Bounds3f, Point3f, Ray, Vector3f}; use shared::core::primitive::{Primitive, PrimitiveTrait}; use shared::core::shape::ShapeIntersection; use shared::utils::math::encode_morton_3; use shared::utils::{find_interval, partition_slice}; -use shared::{gvec_from_slice, Float}; +use shared::{gvec, gvec_from_slice, Float}; use std::cmp::Ordering; use std::sync::atomic::{AtomicUsize, Ordering as AtomicOrdering}; -#[repr(C)] -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum SplitMethod { - SAH, - Hlbvh, - Middle, - EqualCounts, -} - #[repr(C)] #[derive(Debug, Default, Clone, Copy, PartialEq)] struct BVHSplitBucket { @@ -112,51 +103,27 @@ impl BVHBuildNode { } } -pub struct SharedPrimitiveBuffer<'a, P> { - ptr: *mut P, - pub offset: &'a AtomicUsize, - _marker: std::marker::PhantomData<&'a mut [P]>, +pub trait CreateBVH { + fn new(primitives: Vec, max_prims_in_node: usize, split_method: SplitMethod) -> Self; + fn build_hlbvh( + bvh_primitives: &[BVHPrimitiveInfo], + total_nodes: &AtomicUsize, + _original_primitives: &[Primitive], + max_prims_in_node: usize, + ) -> Box; + fn emit_lbvh( + bvh_primitives: &[BVHPrimitiveInfo], + morton_prims: &[MortonPrimitive], + total_nodes: &mut usize, + bit_index: i32, + max_prims_in_node: usize, + ) -> Box; + fn build_upper_sah(nodes: &mut [BVHBuildNode], total_nodes: &AtomicUsize) -> Box; } -unsafe impl<'a, P> Sync for SharedPrimitiveBuffer<'a, P> {} -unsafe impl<'a, P> Send for SharedPrimitiveBuffer<'a, P> {} - -impl<'a, P> SharedPrimitiveBuffer<'a, P> { - pub fn new(slice: &'a mut [P], offset: &'a AtomicUsize) -> Self { - Self { - ptr: slice.as_mut_ptr(), - offset, - _marker: std::marker::PhantomData, - } - } - - pub fn append(&self, primitives: &[P], indices: &[BVHPrimitiveInfo]) -> usize - where - P: Clone, - { - let count = indices.len(); - let start_index = self.offset.fetch_add(count, AtomicOrdering::Relaxed); - - unsafe { - for (i, info) in indices.iter().enumerate() { - let target_ptr = self.ptr.add(start_index + i); - std::ptr::write(target_ptr, primitives[info.primitive_number].clone()); - } - } - start_index - } -} - -pub struct BVHAggregate { - pub max_prims_in_node: usize, - pub primitives: Vec

, - pub split_method: SplitMethod, - pub nodes: Vec, -} - -impl BVHAggregate

{ - pub fn new( - mut primitives: Vec

, +impl CreateBVH for BVHAggregate { + fn new( + mut primitives: Vec, max_prims_in_node: usize, split_method: SplitMethod, ) -> Self { @@ -164,10 +131,11 @@ impl BVHAggregate

{ if primitives.is_empty() { return Self { - max_prims_in_node, - primitives, + max_prims_in_node: max_prims_in_node.try_into().unwrap(), + node_count: 0, + primitives: gvec_from_slice(&primitives), split_method, - nodes: Vec::new(), + nodes: gvec(), }; } @@ -177,7 +145,7 @@ impl BVHAggregate

{ .map(|(i, p)| BVHPrimitiveInfo::new(i, p.bounds())) .collect(); - let total_nodes_count: usize; + let node_count: usize; let root: Box; match split_method { @@ -189,124 +157,46 @@ impl BVHAggregate

{ &primitives, max_prims_in_node, ); - total_nodes_count = nodes_counter.load(AtomicOrdering::Relaxed); + node_count = nodes_counter.load(AtomicOrdering::Relaxed); } _ => { let nodes_counter = AtomicUsize::new(0); - root = Self::build_recursive( + root = build_recursive( &mut primitive_info, &nodes_counter, &primitives, max_prims_in_node, split_method, ); - total_nodes_count = nodes_counter.load(AtomicOrdering::Relaxed); + node_count = nodes_counter.load(AtomicOrdering::Relaxed); } }; // Walk the tree and collect primitive indices in the exact order // the linear layout will visit them (left-to-right, depth-first) - let mut leaf_order = Vec::with_capacity(primitives.len()); - Self::leaf_order(&root, &mut leaf_order); - Self::reorder(&mut primitives, &leaf_order); - drop(leaf_order); + let mut leaf_vec = Vec::with_capacity(primitives.len()); + leaf_order(&root, &mut leaf_vec); + reorder(&mut primitives, &leaf_vec); + drop(leaf_vec); - let mut nodes = vec![LinearBVHNode::default(); total_nodes_count]; + let mut nodes = vec![LinearBVHNode::default(); node_count]; let mut offset = 0; let mut prim_offset = 0; - Self::flatten(&root, &mut nodes, &mut offset, &mut prim_offset); + flatten(&root, &mut nodes, &mut offset, &mut prim_offset); Self { - max_prims_in_node, - primitives, + node_count: node_count.try_into().unwrap(), + max_prims_in_node: max_prims_in_node.try_into().unwrap(), split_method, - nodes, + primitives: gvec_from_slice(&primitives), + nodes: gvec_from_slice(&nodes), } } - fn reorder(primitives: &mut [P], order: &[usize]) { - let n = primitives.len(); - assert_eq!(n, order.len()); - - let mut done = vec![false; n]; - for i in 0..n { - if done[i] || order[i] == i { - done[i] = true; - continue; - } - - let mut prev = i; - let mut curr = order[i]; - while curr != i { - primitives.swap(prev, curr); - done[prev] = true; - prev = curr; - curr = order[prev]; - } - done[prev] = true; - } - } - - fn leaf_order(node: &BVHBuildNode, out: &mut Vec) { - match node { - BVHBuildNode::Leaf { - primitive_indices, .. - } => { - out.extend_from_slice(primitive_indices); - } - BVHBuildNode::Interior { children, .. } => { - Self::leaf_order(&children[0], out); - Self::leaf_order(&children[1], out); - } - } - } - - fn flatten( - node: &BVHBuildNode, - nodes: &mut [LinearBVHNode], - offset: &mut usize, - prim_offset: &mut usize, - ) -> usize { - let local_offset = *offset; - *offset += 1; - - match node { - BVHBuildNode::Leaf { - n_primitives, - bounds, - .. - } => { - let n = *n_primitives; - let linear_node = &mut nodes[local_offset]; - linear_node.bounds = *bounds; - linear_node.n_primitives = n as u16; - linear_node.primitives_offset = *prim_offset; - linear_node.axis = 0; // Irrelevant for leaves - *prim_offset += n; - } - - BVHBuildNode::Interior { - split_axis, - children, - bounds, - } => { - nodes[local_offset].bounds = *bounds; - nodes[local_offset].axis = *split_axis; - nodes[local_offset].n_primitives = 0; - - Self::flatten(&children[0], nodes, offset, prim_offset); - let second_child_offset = Self::flatten(&children[1], nodes, offset, prim_offset); - nodes[local_offset].primitives_offset = second_child_offset; - } - } - - local_offset - } - - pub fn build_hlbvh( + fn build_hlbvh( bvh_primitives: &[BVHPrimitiveInfo], total_nodes: &AtomicUsize, - _original_primitives: &[P], + _original_primitives: &[Primitive], max_prims_in_node: usize, ) -> Box { let bounds = bvh_primitives @@ -586,327 +476,252 @@ impl BVHAggregate

{ )) } } +} - fn build_recursive( - bvh_primitives: &mut [BVHPrimitiveInfo], - total_nodes: &AtomicUsize, - original_primitives: &[P], - max_prims_in_node: usize, - split_method: SplitMethod, - ) -> Box { - total_nodes.fetch_add(1, AtomicOrdering::Relaxed); - let bounds = bvh_primitives - .iter() - .fold(Bounds3f::default(), |b, p| b.union(p.bounds)); +fn build_recursive( + bvh_primitives: &mut [BVHPrimitiveInfo], + total_nodes: &AtomicUsize, + original_primitives: &[Primitive], + max_prims_in_node: usize, + split_method: SplitMethod, +) -> Box { + total_nodes.fetch_add(1, AtomicOrdering::Relaxed); + let bounds = bvh_primitives + .iter() + .fold(Bounds3f::default(), |b, p| b.union(p.bounds)); - let n_primitives = bvh_primitives.len(); - if bounds.surface_area() == 0.0 || n_primitives == 1 || n_primitives <= max_prims_in_node { - let indices: Vec = bvh_primitives.iter().map(|p| p.primitive_number).collect(); - return Box::new(BVHBuildNode::new_leaf(n_primitives, bounds, indices)); - } - let centroid_bounds = bvh_primitives.iter().fold(Bounds3f::default(), |b, p| { - b.union_point(p.bounds.centroid()) - }); + let n_primitives = bvh_primitives.len(); + if bounds.surface_area() == 0.0 || n_primitives == 1 || n_primitives <= max_prims_in_node { + let indices: Vec = bvh_primitives.iter().map(|p| p.primitive_number).collect(); + return Box::new(BVHBuildNode::new_leaf(n_primitives, bounds, indices)); + } + let centroid_bounds = bvh_primitives.iter().fold(Bounds3f::default(), |b, p| { + b.union_point(p.bounds.centroid()) + }); - let dim = centroid_bounds.max_dimension(); - if centroid_bounds.p_max[dim] == centroid_bounds.p_min[dim] { - let indices: Vec = bvh_primitives.iter().map(|p| p.primitive_number).collect(); - return Box::new(BVHBuildNode::new_leaf(n_primitives, bounds, indices)); - } + let dim = centroid_bounds.max_dimension(); + if centroid_bounds.p_max[dim] == centroid_bounds.p_min[dim] { + let indices: Vec = bvh_primitives.iter().map(|p| p.primitive_number).collect(); + return Box::new(BVHBuildNode::new_leaf(n_primitives, bounds, indices)); + } - let mut mid: usize; - match split_method { - SplitMethod::Middle => { - let pmid = (centroid_bounds.p_min[dim] + centroid_bounds.p_max[dim]) / 2.; - mid = partition_slice(bvh_primitives, |p| p.centroid[dim] < pmid); + let mut mid: usize; + match split_method { + SplitMethod::Middle => { + let pmid = (centroid_bounds.p_min[dim] + centroid_bounds.p_max[dim]) / 2.; + mid = partition_slice(bvh_primitives, |p| p.centroid[dim] < pmid); - if mid != 0 && mid != n_primitives { - } else { - mid = n_primitives / 2; - bvh_primitives.select_nth_unstable_by(mid, |a, b| { - a.centroid[dim].partial_cmp(&b.centroid[dim]).unwrap() - }); - } - } - SplitMethod::EqualCounts => { + if mid != 0 && mid != n_primitives { + } else { mid = n_primitives / 2; bvh_primitives.select_nth_unstable_by(mid, |a, b| { a.centroid[dim].partial_cmp(&b.centroid[dim]).unwrap() }); } - SplitMethod::SAH | _ => { - if n_primitives < 2 { - mid = n_primitives / 2; - bvh_primitives.select_nth_unstable_by(mid, |a, b| { - a.centroid[dim] - .partial_cmp(&b.centroid[dim]) - .unwrap_or(Ordering::Equal) - }); - } else { - const N_BUCKETS: usize = 12; - let mut buckets = [BVHSplitBucket::default(); N_BUCKETS]; - for prim in bvh_primitives.iter() { - let mut b = (N_BUCKETS as Float - * centroid_bounds.offset(&prim.centroid)[dim]) + } + SplitMethod::EqualCounts => { + mid = n_primitives / 2; + bvh_primitives.select_nth_unstable_by(mid, |a, b| { + a.centroid[dim].partial_cmp(&b.centroid[dim]).unwrap() + }); + } + SplitMethod::SAH | _ => { + if n_primitives < 2 { + mid = n_primitives / 2; + bvh_primitives.select_nth_unstable_by(mid, |a, b| { + a.centroid[dim] + .partial_cmp(&b.centroid[dim]) + .unwrap_or(Ordering::Equal) + }); + } else { + const N_BUCKETS: usize = 12; + let mut buckets = [BVHSplitBucket::default(); N_BUCKETS]; + for prim in bvh_primitives.iter() { + let mut b = + (N_BUCKETS as Float * centroid_bounds.offset(&prim.centroid)[dim]) as usize; + if b == N_BUCKETS { + b = N_BUCKETS - 1; + } + buckets[b].count += 1; + buckets[b].bounds = buckets[b].bounds.union(prim.bounds); + } + // Compute costs for splitting after each bucket> + const N_SPLITS: usize = N_BUCKETS - 1; + let mut costs = [0.0 as Float; N_SPLITS]; + let mut count_below = 0; + let mut bound_below = Bounds3f::default(); + for i in 0..N_SPLITS { + bound_below = bound_below.union(buckets[i].bounds); + count_below += buckets[i].count; + costs[i] += count_below as Float * bound_below.surface_area(); + } + // Finish initializing costs using a backward scan over splits + let mut count_above = 0; + let mut bound_above = Bounds3f::default(); + for i in (0..N_SPLITS).rev() { + bound_above = bound_above.union(buckets[i + 1].bounds); + count_above += buckets[i + 1].count; + costs[i] += count_above as Float * bound_above.surface_area(); + } + + // Find bucket to split at that minimizes SAH metric> + let mut min_cost = Float::INFINITY; + let mut min_cost_split_bucket = 0; + for (i, &cost) in costs.iter().enumerate().take(N_SPLITS) { + if cost < min_cost { + min_cost = cost; + min_cost_split_bucket = i; + } + } + + // Compute leaf cost and SAH split cost for chosen split + let leaf_cost = n_primitives as Float; + min_cost = 0.5 + min_cost / bounds.surface_area(); + + // Either create leaf or split primitives at selected SAH bucket> + if n_primitives > max_prims_in_node || min_cost < leaf_cost { + mid = partition_slice(bvh_primitives, |bp| { + let mut b = (N_BUCKETS as Float * centroid_bounds.offset(&bp.centroid)[dim]) as usize; if b == N_BUCKETS { b = N_BUCKETS - 1; } - buckets[b].count += 1; - buckets[b].bounds = buckets[b].bounds.union(prim.bounds); - } - // Compute costs for splitting after each bucket> - const N_SPLITS: usize = N_BUCKETS - 1; - let mut costs = [0.0 as Float; N_SPLITS]; - let mut count_below = 0; - let mut bound_below = Bounds3f::default(); - for i in 0..N_SPLITS { - bound_below = bound_below.union(buckets[i].bounds); - count_below += buckets[i].count; - costs[i] += count_below as Float * bound_below.surface_area(); - } - // Finish initializing costs using a backward scan over splits - let mut count_above = 0; - let mut bound_above = Bounds3f::default(); - for i in (0..N_SPLITS).rev() { - bound_above = bound_above.union(buckets[i + 1].bounds); - count_above += buckets[i + 1].count; - costs[i] += count_above as Float * bound_above.surface_area(); - } - - // Find bucket to split at that minimizes SAH metric> - let mut min_cost = Float::INFINITY; - let mut min_cost_split_bucket = 0; - for (i, &cost) in costs.iter().enumerate().take(N_SPLITS) { - if cost < min_cost { - min_cost = cost; - min_cost_split_bucket = i; - } - } - - // Compute leaf cost and SAH split cost for chosen split - let leaf_cost = n_primitives as Float; - min_cost = 0.5 + min_cost / bounds.surface_area(); - - // Either create leaf or split primitives at selected SAH bucket> - if n_primitives > max_prims_in_node || min_cost < leaf_cost { - mid = partition_slice(bvh_primitives, |bp| { - let mut b = (N_BUCKETS as Float - * centroid_bounds.offset(&bp.centroid)[dim]) - as usize; - if b == N_BUCKETS { - b = N_BUCKETS - 1; - } - b <= min_cost_split_bucket + b <= min_cost_split_bucket + }); + if mid == 0 || mid == n_primitives { + mid = n_primitives / 2; + bvh_primitives.select_nth_unstable_by(mid, |a, b| { + a.centroid[dim] + .partial_cmp(&b.centroid[dim]) + .unwrap_or(Ordering::Equal) }); - if mid == 0 || mid == n_primitives { - mid = n_primitives / 2; - bvh_primitives.select_nth_unstable_by(mid, |a, b| { - a.centroid[dim] - .partial_cmp(&b.centroid[dim]) - .unwrap_or(Ordering::Equal) - }); - } - } else { - let indices: Vec = - bvh_primitives.iter().map(|p| p.primitive_number).collect(); - return Box::new(BVHBuildNode::new_leaf(n_primitives, bounds, indices)); } + } else { + let indices: Vec = + bvh_primitives.iter().map(|p| p.primitive_number).collect(); + return Box::new(BVHBuildNode::new_leaf(n_primitives, bounds, indices)); } } - }; + } + }; - let (left_prims, right_prims) = bvh_primitives.split_at_mut(mid); - let build_leaf = |prims: &mut [BVHPrimitiveInfo]| -> Box { - Self::build_recursive( - prims, - total_nodes, - original_primitives, - max_prims_in_node, - split_method, - ) - }; + let (left_prims, right_prims) = bvh_primitives.split_at_mut(mid); + let build_leaf = |prims: &mut [BVHPrimitiveInfo]| -> Box { + build_recursive( + prims, + total_nodes, + original_primitives, + max_prims_in_node, + split_method, + ) + }; - let (child0, child1) = if n_primitives > 128 * 1024 { - rayon::join(|| build_leaf(left_prims), || build_leaf(right_prims)) - } else { - (build_leaf(left_prims), build_leaf(right_prims)) - }; + let (child0, child1) = if n_primitives > 128 * 1024 { + rayon::join(|| build_leaf(left_prims), || build_leaf(right_prims)) + } else { + (build_leaf(left_prims), build_leaf(right_prims)) + }; - let axis = dim as u8; - Box::new(BVHBuildNode::new_interior(axis, child0, child1)) + let axis = dim as u8; + Box::new(BVHBuildNode::new_interior(axis, child0, child1)) +} + +fn flatten( + node: &BVHBuildNode, + nodes: &mut [LinearBVHNode], + offset: &mut usize, + prim_offset: &mut usize, +) -> usize { + let local_offset = *offset; + *offset += 1; + + match node { + BVHBuildNode::Leaf { + n_primitives, + bounds, + .. + } => { + let n = *n_primitives; + let linear_node = &mut nodes[local_offset]; + linear_node.bounds = *bounds; + linear_node.n_primitives = n as u16; + linear_node.primitives_offset = *prim_offset; + linear_node.axis = 0; // Irrelevant for leaves + *prim_offset += n; + } + + BVHBuildNode::Interior { + split_axis, + children, + bounds, + } => { + nodes[local_offset].bounds = *bounds; + nodes[local_offset].axis = *split_axis; + nodes[local_offset].n_primitives = 0; + + flatten(&children[0], nodes, offset, prim_offset); + let second_child_offset = flatten(&children[1], nodes, offset, prim_offset); + nodes[local_offset].primitives_offset = second_child_offset; + } } - pub fn intersect(&self, r: &Ray, t_max: Option) -> Option { - if self.nodes.is_empty() { - return None; + local_offset +} + +fn reorder(primitives: &mut [Primitive], order: &[usize]) { + let n = primitives.len(); + assert_eq!(n, order.len()); + + let mut done = vec![false; n]; + for i in 0..n { + if done[i] || order[i] == i { + done[i] = true; + continue; } - let mut best_si: Option = None; - - let mut hit_t = t_max.unwrap_or(Float::INFINITY); - - let inv_dir = Vector3f::new(1.0 / r.d.x(), 1.0 / r.d.y(), 1.0 / r.d.z()); - let dir_is_neg = [ - if inv_dir.x() < 0.0 { 1 } else { 0 }, - if inv_dir.y() < 0.0 { 1 } else { 0 }, - if inv_dir.z() < 0.0 { 1 } else { 0 }, - ]; - - let mut to_visit_offset = 0; - let mut current_node_index = 0; - let mut nodes_to_visit = [0usize; 64]; - - loop { - let node = &self.nodes[current_node_index]; - - // Check ray against BVH node bounds using the current closest hit_t - if node - .bounds - .intersect_p(r.o, hit_t, inv_dir, &dir_is_neg) - .is_some() - { - if node.n_primitives > 0 { - // Intersect ray with all primitives in this leaf - for i in 0..node.n_primitives { - let prim_idx = node.primitives_offset + i as usize; - let prim = &self.primitives[prim_idx]; - - if let Some(si) = prim.intersect(r, Some(hit_t)) { - hit_t = si.t_hit(); - best_si = Some(si); - } - } - - if to_visit_offset == 0 { - break; - } - to_visit_offset -= 1; - current_node_index = nodes_to_visit[to_visit_offset]; - } else { - // Check the sign of the ray direction against the split axis - if dir_is_neg[node.axis as usize] == 1 { - // Ray is negative (Right -> Left). - // Near child is Second Child (stored in primitives_offset). - // Far child is First Child (current + 1). - - // Push Far - nodes_to_visit[to_visit_offset] = current_node_index + 1; - to_visit_offset += 1; - - // Visit Near immediately - current_node_index = node.primitives_offset; - } else { - // Ray is positive (Left -> Right). - // Push Far - nodes_to_visit[to_visit_offset] = node.primitives_offset; - to_visit_offset += 1; - - current_node_index += 1; - } - } - } else { - // The ray missed the AABB of this node. Pop stack to try the next node. - if to_visit_offset == 0 { - break; - } - to_visit_offset -= 1; - current_node_index = nodes_to_visit[to_visit_offset]; - } + let mut prev = i; + let mut curr = order[i]; + while curr != i { + primitives.swap(prev, curr); + done[prev] = true; + prev = curr; + curr = order[prev]; } - - best_si - } - - fn intersect_p(&self, r: &Ray, t_max: Option) -> bool { - if self.nodes.is_empty() { - return false; - } - - let t_max = t_max.unwrap_or(Float::INFINITY); - - let inv_dir = Vector3f::new(1.0 / r.d.x(), 1.0 / r.d.y(), 1.0 / r.d.z()); - let dir_is_neg = [ - if inv_dir.x() < 0.0 { 1 } else { 0 }, - if inv_dir.y() < 0.0 { 1 } else { 0 }, - if inv_dir.z() < 0.0 { 1 } else { 0 }, - ]; - - let mut to_visit_offset = 0; - let mut current_node_index = 0; - let mut nodes_to_visit = [0usize; 64]; - - loop { - let node = &self.nodes[current_node_index]; - - // Check AABB - if node - .bounds - .intersect_p(r.o, t_max, inv_dir, &dir_is_neg) - .is_some() - { - if node.n_primitives > 0 { - for i in 0..node.n_primitives { - let prim_idx = node.primitives_offset + i as usize; - let prim = &self.primitives[prim_idx]; - - if prim.intersect_p(r, Some(t_max)) { - return true; - } - } - - // No intersection in this leaf, try next node in stack - if to_visit_offset == 0 { - break; - } - to_visit_offset -= 1; - current_node_index = nodes_to_visit[to_visit_offset]; - } else { - // Standard front-to-back traversal order helps find an occlusion - // closer to the origin faster, potentially saving work. - - if dir_is_neg[node.axis as usize] == 1 { - nodes_to_visit[to_visit_offset] = current_node_index + 1; - to_visit_offset += 1; - current_node_index = node.primitives_offset; - } else { - nodes_to_visit[to_visit_offset] = node.primitives_offset; - to_visit_offset += 1; - current_node_index += 1; - } - } - } else { - if to_visit_offset == 0 { - break; - } - to_visit_offset -= 1; - current_node_index = nodes_to_visit[to_visit_offset]; - } - } - false + done[prev] = true; } } -impl BVHAggregate { - pub fn to_device(&self, arena: &mut Arena) -> DeviceBVHAggregate { - let shared_nodes: Vec = self - .nodes - .iter() - .map(|n| shared::core::aggregates::LinearBVHNode { - bounds: n.bounds, - primitives_offset: n.primitives_offset, - n_primitives: n.n_primitives, - axis: n.axis, - pad: 0, - }) - .collect(); - - DeviceBVHAggregate { - max_prims_in_node: self.max_prims_in_node as u32, - primitives: gvec_from_slice(&self.primitives), - primitive_count: self.primitives.len() as u32, - nodes: gvec_from_slice(&shared_nodes), - node_count: self.nodes.len() as u32, +fn leaf_order(node: &BVHBuildNode, out: &mut Vec) { + match node { + BVHBuildNode::Leaf { + primitive_indices, .. + } => { + out.extend_from_slice(primitive_indices); + } + BVHBuildNode::Interior { children, .. } => { + leaf_order(&children[0], out); + leaf_order(&children[1], out); } } } + +// BVHAggregate *BVHAggregate::Create(std::vector prims, +// const ParameterDictionary ¶meters) { +// std::string splitMethodName = parameters.GetOneString("splitmethod", "sah"); +// BVHAggregate::SplitMethod splitMethod; +// if (splitMethodName == "sah") +// splitMethod = BVHAggregate::SplitMethod::SAH; +// else if (splitMethodName == "hlbvh") +// splitMethod = BVHAggregate::SplitMethod::HLBVH; +// else if (splitMethodName == "middle") +// splitMethod = BVHAggregate::SplitMethod::Middle; +// else if (splitMethodName == "equal") +// splitMethod = BVHAggregate::SplitMethod::EqualCounts; +// else { +// Warning(R"(BVH split method "%s" unknown. Using "sah".)", splitMethodName); +// splitMethod = BVHAggregate::SplitMethod::SAH; +// } +// +// int maxPrimsInNode = parameters.GetOneInt("maxnodeprims", 4); +// return new BVHAggregate(std::move(prims), maxPrimsInNode, splitMethod); +// } diff --git a/src/core/bssrdf.rs b/src/core/bssrdf.rs deleted file mode 100644 index b9b886c..0000000 --- a/src/core/bssrdf.rs +++ /dev/null @@ -1,40 +0,0 @@ -use shared::Float; -use shared::Ptr; -use shared::core::bssrdf::BSSRDFTable; - -pub struct BSSRDFTableData { - pub rho_samples: Vec, - pub radius_samples: Vec, - pub profile: Vec, - pub rho_eff: Vec, - pub profile_cdf: Vec, -} - -impl BSSRDFTableData { - pub fn new(n_rho_samples: usize, n_radius_samples: usize) -> Self { - let rho_samples: Vec = Vec::with_capacity(n_rho_samples); - let radius_samples: Vec = Vec::with_capacity(n_radius_samples); - let profile: Vec = Vec::with_capacity(n_radius_samples * n_rho_samples); - let rho_eff: Vec = Vec::with_capacity(n_rho_samples); - let profile_cdf: Vec = Vec::with_capacity(n_radius_samples * n_rho_samples); - Self { - rho_samples, - radius_samples, - profile, - rho_eff, - profile_cdf, - } - } - - pub fn view(&self, rho_ptr: *const Float, radius_ptr: *const Float) -> BSSRDFTable { - BSSRDFTable { - rho_samples: rho_ptr.into(), - n_rho: self.rho_samples.len() as u32, - radius_samples: radius_ptr.into(), - n_radius: self.radius_samples.len() as u32, - profile: Ptr::from(self.profile.as_ptr()), - profile_cdf: Ptr::from(self.profile_cdf.as_ptr()), - rho_eff: Ptr::from(self.rho_eff.as_ptr()), - } - } -} diff --git a/src/core/film.rs b/src/core/film.rs index bc57298..205f398 100644 --- a/src/core/film.rs +++ b/src/core/film.rs @@ -236,7 +236,7 @@ pub trait CreateFilmBase { fn create( params: &ParameterDictionary, filter: Filter, - sensor: Option<&PixelSensor>, + sensor: Ptr, loc: &FileLoc, ) -> Result where @@ -247,7 +247,7 @@ impl CreateFilmBase for FilmBase { fn create( params: &ParameterDictionary, filter: Filter, - sensor: Option<&PixelSensor>, + sensor: Ptr, loc: &FileLoc, ) -> Result where @@ -301,7 +301,7 @@ impl CreateFilmBase for FilmBase { pixel_bounds, filter, diagonal: diagonal_mm * 0.001, - sensor: Ptr::from(sensor.unwrap()), + sensor, }) } } diff --git a/src/core/mod.rs b/src/core/mod.rs index d9b383b..79b0578 100644 --- a/src/core/mod.rs +++ b/src/core/mod.rs @@ -1,5 +1,5 @@ pub mod aggregates; -pub mod bssrdf; +// pub mod bssrdf; pub mod camera; pub mod color; pub mod film; @@ -15,3 +15,4 @@ pub mod scene; pub mod shape; pub mod spectrum; pub mod texture; +pub mod render; diff --git a/src/core/render.rs b/src/core/render.rs new file mode 100644 index 0000000..137916a --- /dev/null +++ b/src/core/render.rs @@ -0,0 +1,20 @@ +use crate::core::scene::BasicScene; +use crate::Arena; +use anyhow::Result; +use log::warn; +use shared::core::camera::CameraTrait; + +fn render_scene(scene: &BasicScene, arena: &Arena) -> Result<()> { + let media = scene.create_media(); + let textures = scene.create_textures(arena); + let lights = scene.create_lights() + let (named_materials, materials) = scene.create_materials(&textures, arena)?; + let (aggregate, area_lights) = + scene.create_aggregate(&textures, &named_materials, &materials, arena); + let camera = scene.get_camera().unwrap(); + let film = camera.get_film(); + warn!("Creating integrator"); + let sampler = scene.get_sampler()?; + let integrator = scene.create_integrator(camera, sampler, aggregate, lights, arena); + Ok(()) +} diff --git a/src/core/scene/builder.rs b/src/core/scene/builder.rs index 030fa67..e55b5fe 100644 --- a/src/core/scene/builder.rs +++ b/src/core/scene/builder.rs @@ -1,18 +1,19 @@ -use super::BasicScene; use super::entities::*; -use crate::Arena; +use super::BasicScene; use crate::spectra::get_colorspace_device; use crate::utils::error::FileLoc; use crate::utils::normalize_utf8; use crate::utils::parameters::{ParameterDictionary, ParsedParameterVector}; use crate::utils::parser::{ParserError, ParserTarget}; -use shared::Float; +use crate::Arena; +use anyhow::Context; use shared::core::camera::CameraTransform; use shared::core::geometry::Vector3f; use shared::spectra::RGBColorSpace; use shared::utils::options::RenderingCoordinateSystem; use shared::utils::transform; use shared::utils::transform::{AnimatedTransform, Transform}; +use shared::Float; use std::collections::{HashMap, HashSet}; use std::ops::{Index, IndexMut}; use std::sync::Arc; @@ -279,15 +280,9 @@ impl ParserTarget for BasicSceneBuilder { uz: Float, loc: FileLoc, ) -> Result<(), ParserError> { - let result = transform::look_at((ex, ey, ez), (lx, ly, lz), (ux, uy, uz)); - match result { - Some(t) => { - self.for_active_transforms(|cur| cur * &t); - } - None => { - eprintln!("Error: Could not invert transform at {}", loc); - } - } + let t = transform::look_at((ex, ey, ez), (lx, ly, lz), (ux, uy, uz)) + .with_context(|| format!("at {}", loc))?; + self.for_active_transforms(|cur| cur * &t); Ok(()) } @@ -640,7 +635,10 @@ impl ParserTarget for BasicSceneBuilder { loc: FileLoc, arena: Arc, ) -> Result<(), ParserError> { - eprintln!("TEXTURE: name='{}' type='{}' tex='{}'", orig_name, type_name, tex_name); + eprintln!( + "TEXTURE: name='{}' type='{}' tex='{}'", + orig_name, type_name, tex_name + ); let name = normalize_utf8(orig_name); self.verify_world("Texture", &loc)?; let dict = ParameterDictionary::from_array( diff --git a/src/core/scene/scene.rs b/src/core/scene/scene.rs index 0a9af77..e6b3520 100644 --- a/src/core/scene/scene.rs +++ b/src/core/scene/scene.rs @@ -1,5 +1,6 @@ use super::entities::*; use super::state::*; +use crate::core::aggregates::CreateBVH; use crate::core::camera::CameraFactory; use crate::core::film::FilmFactory; use crate::core::filter::FilterFactory; @@ -9,12 +10,14 @@ use crate::core::primitive::{CreateGeometricPrimitive, CreateSimplePrimitive}; use crate::core::sampler::SamplerFactory; use crate::core::shape::{ShapeFactory, ShapeWithContext}; use crate::core::texture::{FloatTexture, SpectrumTexture}; +use crate::integrators::{CreateIntegrator, PathConfig, PathIntegrator}; use crate::utils::parallel::{run_async, AsyncJob}; use crate::utils::parameters::{NamedTextures, ParameterDictionary, TextureParameterDictionary}; use crate::utils::resolve_filename; use crate::{Arena, ArenaUpload, FileLoc}; use anyhow::{anyhow, Result}; use parking_lot::Mutex; +use shared::core::aggregates::{BVHAggregate, SplitMethod}; use shared::core::camera::{Camera, CameraTransform}; use shared::core::color::LINEAR; use shared::core::film::Film; @@ -26,6 +29,7 @@ use shared::core::primitive::{AnimatedPrimitive, GeometricPrimitive, Primitive, use shared::core::sampler::Sampler; use shared::core::shape::Shape; use shared::core::texture::{GPUFloatTexture, SpectrumType}; +use shared::lights::sampler::LightSampler; use shared::spectra::RGBColorSpace; use shared::{Ptr, Transform}; use std::collections::HashMap; @@ -347,7 +351,7 @@ impl BasicScene { self.instances.lock().extend(uses); } - pub fn create_textures(&self, arena: &mut Arena) -> NamedTextures { + pub fn create_textures(&self, arena: &Arena) -> NamedTextures { let mut state = self.texture_state.lock(); let mut float_textures: HashMap> = HashMap::new(); @@ -408,7 +412,7 @@ impl BasicScene { pub fn create_materials( &self, textures: &NamedTextures, - arena: &mut Arena, + arena: &Arena, ) -> Result<(HashMap, Vec)> { let mut state = self.material_state.lock(); @@ -619,8 +623,8 @@ impl BasicScene { textures: &NamedTextures, named_materials: &HashMap, materials: &[Material], - arena: &mut Arena, - ) -> (Vec, Vec>) { + arena: &Arena, + ) -> (Arc, Vec>) { let entities = self.shapes.lock(); let animated = self.animated_shapes.lock(); let light_state = self.light_state.lock(); @@ -660,7 +664,36 @@ impl BasicScene { ); } - (primitives, area_lights) + let aggregate = if !primitives.is_empty() { + BVHAggregate::new(primitives.clone(), 4, SplitMethod::SAH) + } else { + BVHAggregate::empty() + }; + + let agg_ptr = arena.alloc(aggregate); + + (Arc::new(Primitive::BVH(agg_ptr)), area_lights) + } + + pub fn create_integrator( + &self, + camera: Arc, + sampler: Arc, + aggregate: Arc, + lights: Vec>, + arena: &Arena + ) -> PathIntegrator { + let integrator = &self.integrator.lock().clone().unwrap(); + PathIntegrator::create( + integrator.parameters.clone(), + camera, + sampler, + aggregate, + lights, + PathConfig::SIMPLE, + arena + ) + .expect("Integrator creation has failed") } fn build_primitives_inner( @@ -724,7 +757,7 @@ impl BasicScene { light_state: &LightState, media: &MediaState, film_cs: Option<&RGBColorSpace>, - arena: &mut Arena, + arena: &Arena, primitives: &mut Vec, area_lights: &mut Vec>, ) { @@ -807,7 +840,7 @@ impl BasicScene { light_state: &LightState, media: &MediaState, film_cs: Option<&RGBColorSpace>, - arena: &mut Arena, + arena: &Arena, primitives: &mut Vec, area_lights: &mut Vec>, ) { @@ -973,6 +1006,20 @@ impl BasicScene { Vec::new() } + pub fn create_media(&self) -> HashMap> { + let mut state = self.media_state.lock(); + + if !state.jobs.is_empty() { + let jobs: Vec<(String, AsyncJob)> = state.jobs.drain().collect(); + for (name, job) in jobs { + let medium = Arc::new(job.wait()); + state.map.insert(name, medium); + } + } + + state.map.clone() + } + // Getters pub fn get_camera(&self) -> Result> { diff --git a/src/films/gbuffer.rs b/src/films/gbuffer.rs index 04b1a57..73b8a82 100644 --- a/src/films/gbuffer.rs +++ b/src/films/gbuffer.rs @@ -21,7 +21,8 @@ impl CreateFilm for GBufferFilm { 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, arena)?; - let film_base = FilmBase::create(params, filter, Some(&sensor), loc)?; + let sensor_ptr = arena.alloc(sensor); + let film_base = FilmBase::create(params, filter, sensor_ptr, loc)?; let filename = params.get_one_string("filename", "pbrt.exr")?; if Path::new(&filename).extension() != Some("exr".as_ref()) { diff --git a/src/films/rgb.rs b/src/films/rgb.rs index 6409d48..78f5a51 100644 --- a/src/films/rgb.rs +++ b/src/films/rgb.rs @@ -19,7 +19,8 @@ impl CreateFilm for RGBFilm { 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, arena)?; - let film_base = FilmBase::create(params, filter, Some(&sensor), loc)?; + let sensor_ptr = arena.alloc(sensor); + let film_base = FilmBase::create(params, filter, sensor_ptr, loc)?; let film = RGBFilm::new(film_base, &colorspace, max_component_value, write_fp16); Ok(Film::RGB(film)) } diff --git a/src/films/spectral.rs b/src/films/spectral.rs index e24e3b3..0e64792 100644 --- a/src/films/spectral.rs +++ b/src/films/spectral.rs @@ -23,7 +23,8 @@ impl CreateFilm for SpectralFilm { 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, arena)?; - let film_base = FilmBase::create(params, filter, Some(&sensor), loc)?; + let sensor_ptr = arena.alloc(sensor); + let film_base = FilmBase::create(params, filter, sensor_ptr, loc)?; let filename = params.get_one_string("filename", "pbrt.exr")?; if Path::new(&filename).extension() != Some("exr".as_ref()) { diff --git a/src/integrators/base.rs b/src/integrators/base.rs index 165dbc4..c0e9b53 100644 --- a/src/integrators/base.rs +++ b/src/integrators/base.rs @@ -18,7 +18,24 @@ pub struct IntegratorBase { } impl IntegratorBase { - pub fn new(aggregate: Arc, lights: Vec>) -> Self { + pub fn new(aggregate: Arc, mut lights: Vec>) -> Self { + let scene_bounds = aggregate.bounds(); + + for light in &mut lights { + Arc::get_mut(light) + .expect("Light has multiple owners during setup") + .preprocess(&scene_bounds); + } + + println!( + "IntegratorBase: {} lights, scene_bounds: {:?}", + lights.len(), + scene_bounds + ); + for (i, l) in lights.iter().enumerate() { + println!(" light[{}]: type={:?}", i, l.light_type()); + } + let infinite_lights = lights .iter() .filter(|light| light.light_type().is_infinite()) diff --git a/src/integrators/mod.rs b/src/integrators/mod.rs index 1ea4497..b0f14eb 100644 --- a/src/integrators/mod.rs +++ b/src/integrators/mod.rs @@ -4,13 +4,20 @@ pub mod path; pub mod pipeline; pub mod state; -pub use path::PathIntegrator; +pub use path::{PathConfig, PathIntegrator}; -use crate::Arena; +use crate::lights::sampler::create_light_sampler; +use crate::{Arena, ParameterDictionary}; +use anyhow::Result; +use shared::core::camera::Camera; use shared::core::film::VisibleSurface; use shared::core::geometry::{Point2i, Ray}; +use shared::core::light::Light; +use shared::core::primitive::Primitive; use shared::core::sampler::Sampler; +use shared::lights::sampler::LightSampler; use shared::spectra::{SampledSpectrum, SampledWavelengths}; +use std::sync::Arc; pub trait IntegratorTrait { fn render(&self); @@ -34,3 +41,33 @@ pub trait RayIntegratorTrait { arena: &Arena, ) -> (SampledSpectrum, Option); } + +pub trait CreateIntegrator { + fn create( + parameters: ParameterDictionary, + camera: Arc, + sampler: Arc, + aggregate: Arc, + lights: Vec>, + config: PathConfig, + arena: &Arena, + ) -> Result; +} + +impl CreateIntegrator for PathIntegrator { + fn create( + parameters: ParameterDictionary, + camera: Arc, + sampler: Arc, + aggregate: Arc, + lights: Vec>, + config: PathConfig, + arena: &Arena, + ) -> Result { + let _max_depth = parameters.get_one_int("maxdepth", 5)?; + let _regularize = parameters.get_one_bool("regularize", false)?; + let light_sampler = create_light_sampler("bvh", &lights, arena); + let integrator = PathIntegrator::new(aggregate, lights, camera, light_sampler, config); + Ok(integrator) + } +} diff --git a/src/integrators/path.rs b/src/integrators/path.rs index 0ad517a..c76a612 100644 --- a/src/integrators/path.rs +++ b/src/integrators/path.rs @@ -81,7 +81,7 @@ impl PathIntegrator { sampler: LightSampler, config: PathConfig, ) -> Self { - let base = IntegratorBase::new(aggregate, lights.clone()); + let base = IntegratorBase::new(aggregate, lights); Self { base, camera, diff --git a/src/integrators/pipeline.rs b/src/integrators/pipeline.rs index 5d5f21e..7cfc58c 100644 --- a/src/integrators/pipeline.rs +++ b/src/integrators/pipeline.rs @@ -82,6 +82,7 @@ pub fn render( ) where T: RayIntegratorTrait + Sync, { + println!("RENDER CALLED"); let options = get_options(); if let Some((p_pixel, sample_index)) = options.debug_start { let s_index = sample_index as usize; @@ -101,6 +102,11 @@ pub fn render( } let pixel_bounds = camera.get_film().pixel_bounds(); + println!( + "pixel_bounds: {:?}, area: {}", + pixel_bounds, + pixel_bounds.area() + ); let spp = sampler_prototype.samples_per_pixel(); let total_work = (pixel_bounds.area() as u64) * (spp as u64); let progress = PbrtProgress::new(total_work, "Rendering", options.quiet); @@ -162,6 +168,7 @@ pub fn render( for p_pixel in tile_bounds { for sample_index in wave_start..wave_end { sampler.start_pixel_sample(*p_pixel, sample_index, None); + println!("Evaluating pixel {:?} sample {}", p_pixel, sample_index); evaluate_pixel_sample( integrator, camera, @@ -232,7 +239,7 @@ pub fn evaluate_pixel_sample( camera: &Camera, sampler: &mut Sampler, pixel: Point2i, - _sample_index: usize, + sample_index: usize, arena: &Arena, ) { let mut lu = sampler.get1d(); @@ -267,6 +274,13 @@ pub fn evaluate_pixel_sample( l = SampledSpectrum::new(0.); } + if pixel.x() == 352 && pixel.y() == 352 && sample_index == 0 { + println!("Center pixel: L = {:?}", l); + println!(" ray origin: {:?}", camera_ray.ray.o); + println!(" ray dir: {:?}", camera_ray.ray.d); + println!(" camera_sample.p_film: {:?}", camera_sample.p_film); + } + film.add_sample( pixel, l, diff --git a/src/lights/distant.rs b/src/lights/distant.rs index dcd88ac..53b0b85 100644 --- a/src/lights/distant.rs +++ b/src/lights/distant.rs @@ -15,11 +15,11 @@ use shared::utils::{Ptr, Transform}; use shared::Float; trait CreateDistantLight { - fn new(render_from_light: Transform, le: Spectrum, scale: Float) -> Self; + fn new(render_from_light: Transform, le: Spectrum, scale: Float, arena: &Arena) -> Self; } impl CreateDistantLight for DistantLight { - fn new(render_from_light: Transform, le: Spectrum, scale: Float) -> Self { + fn new(render_from_light: Transform, le: Spectrum, scale: Float, arena: &Arena) -> Self { let base = LightBase::new( LightType::DeltaDirection, render_from_light, @@ -28,7 +28,7 @@ impl CreateDistantLight for DistantLight { let lemit = lookup_spectrum(&le); Self { base, - lemit: Ptr::from(&*lemit), + lemit: arena.alloc_arc(lemit), scale, scene_center: Point3f::default(), scene_radius: 0., @@ -44,7 +44,7 @@ pub fn create( _shape: &Shape, _alpha_text: &FloatTexture, colorspace: Option<&RGBColorSpace>, - _arena: &Arena, + arena: &Arena, ) -> Result { let default_cs = crate::spectra::default_colorspace(); let cs = colorspace.unwrap_or(&default_cs); @@ -88,7 +88,7 @@ pub fn create( scale *= e_v; } - let specific = DistantLight::new(final_render, l, scale); + let specific = DistantLight::new(final_render, l, scale, arena); Ok(Light::Distant(specific)) } diff --git a/src/lights/infinite.rs b/src/lights/infinite.rs index 1096947..167986a 100644 --- a/src/lights/infinite.rs +++ b/src/lights/infinite.rs @@ -3,7 +3,7 @@ use crate::core::light::lookup_spectrum; use crate::core::spectrum::spectrum_to_photometric; use crate::spectra::get_spectra_context; use crate::utils::resolve_filename; -use crate::{Arena, FileLoc, ParameterDictionary, ArenaUpload, Upload}; +use crate::{Arena, FileLoc, ParameterDictionary, ArenaUpload}; use anyhow::{anyhow, Result}; use rayon::prelude::*; use shared::core::camera::CameraTransform; diff --git a/src/lights/sampler.rs b/src/lights/sampler.rs index d9303dc..149e1e4 100644 --- a/src/lights/sampler.rs +++ b/src/lights/sampler.rs @@ -9,7 +9,6 @@ use shared::utils::Ptr; use shared::Float; use std::sync::Arc; -/// Top-level dispatcher matching the C++ LightSampler::Create. pub fn create_light_sampler( name: &str, lights: &[Arc],