diff --git a/shared/src/bxdfs/conductor.rs b/shared/src/bxdfs/conductor.rs index 45b5fd6..5584d9d 100644 --- a/shared/src/bxdfs/conductor.rs +++ b/shared/src/bxdfs/conductor.rs @@ -18,8 +18,6 @@ pub struct ConductorBxDF { pub k: SampledSpectrum, } -unsafe impl Send for ConductorBxDF {} -unsafe impl Sync for ConductorBxDF {} impl ConductorBxDF { pub fn new( diff --git a/shared/src/bxdfs/measured.rs b/shared/src/bxdfs/measured.rs index 27d9078..9ab000e 100644 --- a/shared/src/bxdfs/measured.rs +++ b/shared/src/bxdfs/measured.rs @@ -32,8 +32,6 @@ pub struct MeasuredBxDF { pub lambda: SampledWavelengths, } -unsafe impl Send for MeasuredBxDF {} -unsafe impl Sync for MeasuredBxDF {} impl MeasuredBxDF { pub fn new(brdf: &MeasuredBxDFData, lambda: &SampledWavelengths) -> Self { diff --git a/shared/src/core/color.rs b/shared/src/core/color.rs index 204cef6..7af72d0 100644 --- a/shared/src/core/color.rs +++ b/shared/src/core/color.rs @@ -1117,8 +1117,6 @@ pub struct RGBToSpectrumTable { pub n_nodes: u32, } -unsafe impl Send for RGBToSpectrumTable {} -unsafe impl Sync for RGBToSpectrumTable {} impl RGBToSpectrumTable { #[inline(always)] diff --git a/shared/src/core/film.rs b/shared/src/core/film.rs index c4fa1f8..a0c7283 100644 --- a/shared/src/core/film.rs +++ b/shared/src/core/film.rs @@ -436,8 +436,6 @@ pub struct SpectralFilm { pub bucket_splats: GVec, } -unsafe impl Send for SpectralFilm {} -unsafe impl Sync for SpectralFilm {} impl SpectralFilm { pub fn new( @@ -618,8 +616,6 @@ pub enum Film { Spectral(SpectralFilm), } -unsafe impl Send for Film {} -unsafe impl Sync for Film {} impl Film { pub fn base(&self) -> &FilmBase { diff --git a/shared/src/core/geometry/bounds.rs b/shared/src/core/geometry/bounds.rs index ca74b02..d2e053b 100644 --- a/shared/src/core/geometry/bounds.rs +++ b/shared/src/core/geometry/bounds.rs @@ -261,6 +261,15 @@ impl Bounds2f { } impl Bounds3f { + /// SAH bucket index for `p` along `dim`, in `[0, n_buckets)`. `self` is the + /// centroid bounds, so `offset` is in [0,1] and only `offset == 1` needs the + /// clamp -- same as pbrt's `if (b == nBuckets) b = nBuckets - 1`. + #[inline] + pub fn sah_bucket(&self, p: &Point3f, dim: usize, n_buckets: usize) -> usize { + let offset = self.offset(p)[dim]; + ((n_buckets as Float * offset) as usize).min(n_buckets - 1) + } + #[inline(always)] pub fn intersect_p( &self, diff --git a/shared/src/core/image.rs b/shared/src/core/image.rs index 6f072fc..2998d60 100644 --- a/shared/src/core/image.rs +++ b/shared/src/core/image.rs @@ -122,10 +122,13 @@ impl Pixels { } pub unsafe fn read(&self, texel_offset: usize, encoding: &ColorEncoding) -> Float { - match self.format { - PixelFormat::U8 => encoding.to_linear_scalar(self.read_u8(texel_offset)), - PixelFormat::F16 => f16_to_f32_software(self.read_f16(texel_offset)), - PixelFormat::F32 => self.read_f32(texel_offset), + // SAFETY: `texel_offset` is in range by this fn's own contract. + unsafe { + match self.format { + PixelFormat::U8 => encoding.to_linear_scalar(self.read_u8(texel_offset)), + PixelFormat::F16 => f16_to_f32_software(self.read_f16(texel_offset)), + PixelFormat::F32 => self.read_f32(texel_offset), + } } } @@ -505,12 +508,3 @@ impl FilterFunction { } } -#[repr(C)] -#[derive(Clone, Copy, Debug)] -pub struct ImagePyramid { - pub levels: *const Ptr, - pub level_count: u32, - pub wrap_mode: WrapMode, - pub filter: FilterFunction, - pub max_aniso: f32, -} diff --git a/shared/src/core/interaction.rs b/shared/src/core/interaction.rs index 0d818f1..7cffc05 100644 --- a/shared/src/core/interaction.rs +++ b/shared/src/core/interaction.rs @@ -235,8 +235,6 @@ pub struct SurfaceInteraction { pub dvdy: Float, } -unsafe impl Send for SurfaceInteraction {} -unsafe impl Sync for SurfaceInteraction {} impl SurfaceInteraction { pub fn le( diff --git a/shared/src/core/light.rs b/shared/src/core/light.rs index c71b6c0..ca71193 100644 --- a/shared/src/core/light.rs +++ b/shared/src/core/light.rs @@ -176,7 +176,9 @@ impl LightBase { } #[repr(C)] -#[derive(Debug, Copy, Clone)] +// Default gives phi == 0, which `union` treats as empty -- that is what the SAH +// bucket accumulation starts from. +#[derive(Debug, Copy, Clone, Default)] pub struct LightBounds { pub bounds: Bounds3f, pub phi: Float, @@ -209,7 +211,8 @@ impl LightBounds { impl LightBounds { pub fn centroid(&self) -> Point3f { - self.bounds.p_min + Vector3f::from(self.bounds.p_max) / 2. + // (pMin + pMax) / 2 -- Point has no scalar Div, so go via Vector. + Point3f::from((Vector3f::from(self.bounds.p_min) + Vector3f::from(self.bounds.p_max)) / 2.) } pub fn importance(&self, p: Point3f, n: Normal3f) -> Float { @@ -267,11 +270,12 @@ impl LightBounds { } pub fn union(a: &Self, b: &Self) -> Self { + // If one LightBounds has zero power, return the *other* (lights.h:137). if a.phi == 0. { - return a.clone(); + return *b; } if b.phi == 0. { - return b.clone(); + return *a; } let a_cone = DirectionCone::new(a.w, a.cos_theta_o); @@ -311,9 +315,13 @@ pub trait LightTrait { uv: Point2f, w: Vector3f, lambda: &SampledWavelengths, - ) -> SampledSpectrum; + ) -> SampledSpectrum { + self.base().l(p, n, uv, w, lambda) + } - fn le(&self, ray: &Ray, lambda: &SampledWavelengths) -> SampledSpectrum; + fn le(&self, ray: &Ray, lambda: &SampledWavelengths) -> SampledSpectrum { + self.base().le(ray, lambda) + } fn light_type(&self) -> LightType { self.base().light_type diff --git a/shared/src/core/medium.rs b/shared/src/core/medium.rs index 1c6a832..7727821 100644 --- a/shared/src/core/medium.rs +++ b/shared/src/core/medium.rs @@ -97,8 +97,6 @@ pub struct MajorantGrid { pub n_voxels: u32, } -unsafe impl Send for MajorantGrid {} -unsafe impl Sync for MajorantGrid {} impl MajorantGrid { #[cfg(not(target_os = "cuda"))] @@ -715,8 +713,6 @@ pub struct MediumInterface { pub outside: Ptr, } -unsafe impl Send for MediumInterface {} -unsafe impl Sync for MediumInterface {} impl Default for MediumInterface { fn default() -> Self { diff --git a/shared/src/core/primitive.rs b/shared/src/core/primitive.rs index 7c4342d..b21be85 100644 --- a/shared/src/core/primitive.rs +++ b/shared/src/core/primitive.rs @@ -32,8 +32,6 @@ pub struct GeometricPrimitive { pub alpha: Ptr, } -unsafe impl Send for GeometricPrimitive {} -unsafe impl Sync for GeometricPrimitive {} impl PrimitiveTrait for GeometricPrimitive { fn bounds(&self) -> Bounds3f { diff --git a/shared/src/core/spectrum.rs b/shared/src/core/spectrum.rs index c63b4b3..d1f2df0 100644 --- a/shared/src/core/spectrum.rs +++ b/shared/src/core/spectrum.rs @@ -22,8 +22,6 @@ pub struct StandardSpectra { pub d65: Ptr, } -unsafe impl Send for StandardSpectra {} -unsafe impl Sync for StandardSpectra {} #[repr(C)] #[enum_dispatch(SpectrumTrait)] diff --git a/shared/src/lights/diffuse.rs b/shared/src/lights/diffuse.rs index 48375a9..4532af9 100644 --- a/shared/src/lights/diffuse.rs +++ b/shared/src/lights/diffuse.rs @@ -33,17 +33,7 @@ pub struct DiffuseAreaLight { pub scale: Float, } -unsafe impl Send for DiffuseAreaLight {} -unsafe impl Sync for DiffuseAreaLight {} - impl DiffuseAreaLight { - // fn l_base(&self, n: Normal3f, wo: Vector3f, lambda: &SampledWavelengths) -> SampledSpectrum { - // if !self.two_sided && n.dot(wo.into()) <= 0.0 { - // return SampledSpectrum::new(0.0); - // } - // self.lemit.sample(lambda) * self.scale - // } - fn alpha_masked(&self, intr: &Interaction) -> bool { if self.alpha.is_null() { return false; @@ -129,7 +119,7 @@ impl LightTrait for DiffuseAreaLight { let mut rgb = RGB::default(); uv[1] = 1. - uv[1]; for c in 0..3 { - rgb[c] = self.image.bilerp_channel(uv, c as i32); + rgb[c] = self.image.bilerp_channel(uv, c); } let spec = RGBIlluminantSpectrum::new(&self.colorspace, rgb.clamp_zero()); @@ -140,10 +130,6 @@ impl LightTrait for DiffuseAreaLight { } } - fn le(&self, _ray: &Ray, _lambda: &SampledWavelengths) -> SampledSpectrum { - todo!() - } - #[cfg(not(target_os = "cuda"))] fn phi(&self, lambda: SampledWavelengths) -> SampledSpectrum { let mut l = SampledSpectrum::new(0.); @@ -152,7 +138,7 @@ impl LightTrait for DiffuseAreaLight { for x in 0..self.image.resolution().x() { let mut rgb = RGB::default(); for c in 0..3 { - rgb[c] = self.image.get_channel(Point2i::new(x, y), c as i32); + rgb[c] = self.image.get_channel(Point2i::new(x, y), c); } l += RGBIlluminantSpectrum::new(&self.colorspace, rgb.clamp_zero()) @@ -168,9 +154,7 @@ impl LightTrait for DiffuseAreaLight { } #[cfg(not(target_os = "cuda"))] - fn preprocess(&mut self, _scene_bounds: &Bounds3f) { - return; - } + fn preprocess(&mut self, _scene_bounds: &Bounds3f) {} #[cfg(not(target_os = "cuda"))] fn bounds(&self) -> Option { diff --git a/shared/src/lights/distant.rs b/shared/src/lights/distant.rs index 11247da..9d26473 100644 --- a/shared/src/lights/distant.rs +++ b/shared/src/lights/distant.rs @@ -6,7 +6,7 @@ use crate::core::light::{LightBase, LightBounds, LightLiSample, LightSampleConte use crate::core::spectrum::SpectrumTrait; use crate::spectra::{DenselySampledSpectrum, SampledSpectrum, SampledWavelengths}; use crate::utils::math::square; -use crate::{Float, Ptr, PI}; +use crate::{Float, PI, Ptr}; use num_traits::Float as NumFloat; #[repr(C)] @@ -75,21 +75,6 @@ impl LightTrait for DistantLight { 0. } - fn l( - &self, - _p: Point3f, - _n: Normal3f, - _uv: Point2f, - _w: Vector3f, - _lambda: &SampledWavelengths, - ) -> SampledSpectrum { - todo!() - } - - fn le(&self, _ray: &Ray, _lambda: &SampledWavelengths) -> SampledSpectrum { - todo!() - } - fn preprocess(&mut self, scene_bounds: &Bounds3f) { let (center, radius) = scene_bounds.bounding_sphere(); self.scene_center = center; diff --git a/shared/src/lights/goniometric.rs b/shared/src/lights/goniometric.rs index 1abf00a..9881f31 100644 --- a/shared/src/lights/goniometric.rs +++ b/shared/src/lights/goniometric.rs @@ -51,28 +51,29 @@ impl LightTrait for GoniometricLight { 0. } - fn l( - &self, - _p: Point3f, - _n: Normal3f, - _uv: Point2f, - _w: Vector3f, - _lambda: &SampledWavelengths, - ) -> SampledSpectrum { - todo!() - } - fn le(&self, _ray: &Ray, _lambda: &SampledWavelengths) -> SampledSpectrum { - todo!() - } + fn preprocess(&mut self, _scene_bounds: &Bounds3f) {} - #[cfg(not(target_os = "cuda"))] - fn preprocess(&mut self, _scene_bounds: &Bounds3f) { - todo!() - } - - #[cfg(not(target_os = "cuda"))] fn bounds(&self) -> Option { - todo!() + let mut sum_y = 0.; + for x in 0..self.image.resolution().x() { + for y in 0..self.image.resolution().y() { + sum_y += self.image.get_channel(Point2i::new(x, y), 0); + } + } + let phi = self.scale * self.iemit.max_value() * 4. * PI * sum_y + / (self.image.resolution().x() * self.image.resolution().y()) as f32; + let p = self + .base() + .render_from_light + .apply_to_point(Point3f::new(0., 0., 0.)); + Some(LightBounds::new( + &Bounds3f::from_points(p, p), + Vector3f::new(0., 0., 1.), + phi, + PI.cos(), + (PI / 2.).cos(), + false, + )) } #[cfg(not(target_os = "cuda"))] diff --git a/shared/src/lights/infinite.rs b/shared/src/lights/infinite.rs index e8bef27..db83094 100644 --- a/shared/src/lights/infinite.rs +++ b/shared/src/lights/infinite.rs @@ -15,8 +15,8 @@ use crate::spectra::{DenselySampledSpectrum, SampledSpectrum, SampledWavelengths use crate::spectra::{RGBColorSpace, RGBIlluminantSpectrum}; use crate::utils::math::{clamp, equal_area_sphere_to_square, equal_area_square_to_sphere, square}; use crate::utils::sampling::{ - sample_uniform_sphere, uniform_sphere_pdf, AliasTable, PiecewiseConstant2D, - WindowedPiecewiseConstant2D, + AliasTable, PiecewiseConstant2D, WindowedPiecewiseConstant2D, sample_uniform_sphere, + uniform_sphere_pdf, }; use crate::utils::{Ptr, Transform}; use crate::{Float, PI}; @@ -32,9 +32,6 @@ pub struct UniformInfiniteLight { pub scene_radius: Float, } -unsafe impl Send for UniformInfiniteLight {} -unsafe impl Sync for UniformInfiniteLight {} - impl UniformInfiniteLight { pub fn new( render_from_light: Transform, @@ -100,29 +97,16 @@ impl LightTrait for UniformInfiniteLight { uniform_sphere_pdf() } - fn l( - &self, - _p: Point3f, - _n: Normal3f, - _uv: Point2f, - _w: Vector3f, - _lambda: &SampledWavelengths, - ) -> SampledSpectrum { - todo!() - } - fn le(&self, _ray: &Ray, lambda: &SampledWavelengths) -> SampledSpectrum { self.scale * self.lemit.sample(lambda) } - #[cfg(not(target_os = "cuda"))] - fn preprocess(&mut self, _scene_bounds: &Bounds3f) { - todo!() + fn preprocess(&mut self, scene_bounds: &Bounds3f) { + (self.scene_center, self.scene_radius) = scene_bounds.bounding_sphere(); } - #[cfg(not(target_os = "cuda"))] fn bounds(&self) -> Option { - todo!() + None } #[cfg(not(target_os = "cuda"))] @@ -144,9 +128,6 @@ pub struct ImageInfiniteLight { pub scene_center: Point3f, } -unsafe impl Send for ImageInfiniteLight {} -unsafe impl Sync for ImageInfiniteLight {} - impl ImageInfiniteLight { pub fn new( render_from_light: Transform, @@ -236,17 +217,6 @@ impl LightTrait for ImageInfiniteLight { pdf / (4. * PI) } - fn l( - &self, - _p: Point3f, - _n: Normal3f, - _uv: Point2f, - _w: Vector3f, - _lambda: &SampledWavelengths, - ) -> SampledSpectrum { - todo!() - } - fn le(&self, ray: &Ray, lambda: &SampledWavelengths) -> SampledSpectrum { let w_light = self .base @@ -279,14 +249,10 @@ impl LightTrait for ImageInfiniteLight { 4. * PI * PI * square(self.scene_radius) * self.scale * sum_l / (width * height) as Float } - #[cfg(not(target_os = "cuda"))] fn preprocess(&mut self, scene_bounds: &Bounds3f) { - let (scene_center, scene_radius) = scene_bounds.bounding_sphere(); - self.scene_center = scene_center; - self.scene_radius = scene_radius; + (self.scene_center, self.scene_radius) = scene_bounds.bounding_sphere(); } - #[cfg(not(target_os = "cuda"))] fn bounds(&self) -> Option { None } @@ -428,17 +394,6 @@ impl LightTrait for PortalInfiniteLight { pdf / duv_dw } - fn l( - &self, - _p: Point3f, - _n: Normal3f, - _uv: Point2f, - _w: Vector3f, - _lambda: &SampledWavelengths, - ) -> SampledSpectrum { - todo!() - } - fn le(&self, ray: &Ray, lambda: &SampledWavelengths) -> SampledSpectrum { let uv = self.image_from_render(ray.d.normalize()); let b = self.image_bounds(ray.o); diff --git a/shared/src/lights/point.rs b/shared/src/lights/point.rs index d590b8b..b35eda4 100644 --- a/shared/src/lights/point.rs +++ b/shared/src/lights/point.rs @@ -7,7 +7,7 @@ use crate::core::light::{ }; use crate::core::spectrum::SpectrumTrait; use crate::spectra::{DenselySampledSpectrum, SampledSpectrum, SampledWavelengths}; -use crate::{Float, PI, Ptr, Transform}; +use crate::{Float, INV_2_PI, PI, Ptr, Transform}; use num_traits::Float as NumFloat; #[repr(C)] @@ -51,30 +51,12 @@ impl LightTrait for PointLight { 0. } - fn l( - &self, - _p: Point3f, - _n: Normal3f, - _uv: Point2f, - _w: Vector3f, - _lambda: &SampledWavelengths, - ) -> SampledSpectrum { - todo!() - } - - fn le(&self, _ray: &Ray, _lambda: &SampledWavelengths) -> SampledSpectrum { - todo!() - } - #[cfg(not(target_os = "cuda"))] fn phi(&self, lambda: SampledWavelengths) -> SampledSpectrum { 4. * PI * self.scale * self.i.sample(&lambda) } - #[cfg(not(target_os = "cuda"))] - fn preprocess(&mut self, _scene_bounds: &Bounds3f) { - todo!() - } + fn preprocess(&mut self, _scene_bounds: &Bounds3f) {} #[cfg(not(target_os = "cuda"))] fn bounds(&self) -> Option { @@ -88,7 +70,7 @@ impl LightTrait for PointLight { Vector3f::new(0., 0., 1.), phi, PI.cos(), - (PI / 2.).cos(), + INV_2_PI.cos(), false, )) } diff --git a/shared/src/lights/projection.rs b/shared/src/lights/projection.rs index 28bc875..36ab79f 100644 --- a/shared/src/lights/projection.rs +++ b/shared/src/lights/projection.rs @@ -75,21 +75,6 @@ impl LightTrait for ProjectionLight { todo!() } - fn l( - &self, - _p: Point3f, - _n: Normal3f, - _uv: Point2f, - _w: Vector3f, - _lambda: &SampledWavelengths, - ) -> SampledSpectrum { - todo!() - } - - fn le(&self, _ray: &Ray, _lambda: &SampledWavelengths) -> SampledSpectrum { - todo!() - } - fn phi(&self, lambda: SampledWavelengths) -> SampledSpectrum { let mut sum = SampledSpectrum::new(0.); let res = self.image.resolution(); @@ -118,11 +103,48 @@ impl LightTrait for ProjectionLight { self.scale * self.a * sum / (res.x() * res.y()) as Float } - fn preprocess(&mut self, _scene_bounds: &Bounds3f) { - todo!() - } + fn preprocess(&mut self, _scene_bounds: &Bounds3f) {} fn bounds(&self) -> Option { - todo!() + let mut sum = 0.; + for v in 0..self.image.resolution().y() { + for u in 0..self.image.resolution().x() { + let uv = Point2i::new(u, v); + sum += self.image.get_channel(uv, 0).max( + self.image + .get_channel(uv, 1) + .max(self.image.get_channel(uv, 2)), + ); + } + } + + let phi = + self.scale * sum / (self.image.resolution().x() * self.image.resolution().y()) as f32; + + let p_corner = Point3f::new( + self.screen_bounds.p_max.x(), + self.screen_bounds.p_max.y(), + 0., + ); + + let w_corner = Vector3f::from(self.light_from_screen.apply_to_point(p_corner)).normalize(); + let cos_total_width = cos_theta(w_corner); + + let p = self + .base + .render_from_light + .apply_to_point(Point3f::new(0., 0., 0.)); + let w = self + .base + .render_from_light + .apply_to_vector(Vector3f::new(0., 0., 1.)); + Some(LightBounds::new( + &Bounds3f::from_points(p, p), + w, + phi, + 1., + cos_total_width, + false, + )) } } diff --git a/shared/src/lights/sampler.rs b/shared/src/lights/sampler.rs index aaf1146..cfeb815 100644 --- a/shared/src/lights/sampler.rs +++ b/shared/src/lights/sampler.rs @@ -164,27 +164,25 @@ impl CompactLightBounds { } } -#[derive(Debug, Clone)] +#[repr(C)] +#[derive(Debug, Clone, Copy)] pub struct SampledLight { pub light: LightIdx, pub p: Float, } -// impl SampledLight { -// pub fn new(light: Light, p: Float) -> Self { -// Self { -// light: Ptr::from(&light), -// p, -// } -// } -// } -// #[enum_dispatch] pub trait LightSamplerTrait { - fn sample_with_context(&self, ctx: &LightSampleContext, u: Float) -> Option; - fn pmf_with_context(&self, ctx: &LightSampleContext, idx: LightIdx) -> Float; fn sample(&self, u: Float) -> Option; fn pmf(&self, idx: LightIdx) -> Float; + + /// Samplers that ignore the shading context inherit these. + fn sample_with_context(&self, _ctx: &LightSampleContext, u: Float) -> Option { + self.sample(u) + } + fn pmf_with_context(&self, _ctx: &LightSampleContext, idx: LightIdx) -> Float { + self.pmf(idx) + } } #[derive(Clone, Debug)] @@ -195,7 +193,8 @@ pub enum LightSampler { BVH(BVHLightSampler), } -#[derive(Clone, Debug)] +#[repr(C)] +#[derive(Clone, Copy, Debug)] pub struct UniformLightSampler { lights_len: u32, } @@ -207,10 +206,6 @@ impl UniformLightSampler { } impl LightSamplerTrait for UniformLightSampler { - fn sample_with_context(&self, _ctx: &LightSampleContext, u: Float) -> Option { - self.sample(u) - } - fn sample(&self, u: Float) -> Option { if self.lights_len == 0 { return None; @@ -222,10 +217,6 @@ impl LightSamplerTrait for UniformLightSampler { }) } - fn pmf_with_context(&self, _ctx: &LightSampleContext, _idx: LightIdx) -> Float { - self.pmf(_idx) - } - fn pmf(&self, _idx: LightIdx) -> Float { if self.lights_len == 0 { return 0.0; @@ -237,22 +228,10 @@ impl LightSamplerTrait for UniformLightSampler { #[repr(C)] #[derive(Clone, Debug, Copy)] pub struct PowerLightSampler { - pub lights_len: u32, pub alias_table: Ptr, } -unsafe impl Send for PowerLightSampler {} -unsafe impl Sync for PowerLightSampler {} - impl LightSamplerTrait for PowerLightSampler { - fn sample_with_context(&self, _ctx: &LightSampleContext, u: Float) -> Option { - self.sample(u) - } - - fn pmf_with_context(&self, _ctx: &LightSampleContext, idx: LightIdx) -> Float { - self.pmf(idx) - } - fn sample(&self, u: Float) -> Option { if self.alias_table.size() == 0 { return None; @@ -333,17 +312,20 @@ impl LightBVHNode { pub fn child_or_light_index(&self) -> u32 { self.packed_data & Self::INDEX_MASK } - - pub fn sample(&self, _ctx: &LightSampleContext, _u: Float) -> Option { - todo!("Implement LightBVHNode::Sample logic") - } } +/// Canary value stored in `bit_trails` for a light that is not a BVH leaf, i.e. an +/// infinite light or one with negative `phi`. Stands in for pbrt's +/// `lightToBitTrail.HasKey(light)`. +pub const NO_BIT_TRAIL: u64 = u64::MAX; + #[derive(Clone, Debug, Copy)] pub struct BVHLightSampler { pub nodes: Ptr, - pub lights: Ptr, - pub infinite_lights: Ptr, + /// Handles of the infinite lights, in scene order. + pub infinite_lights: Ptr, + /// Indexed by *global* light index, matching the leaf indices stored in + /// `nodes`; `NO_BIT_TRAIL` where the light has no leaf. pub bit_trails: Ptr, pub nodes_len: u32, pub lights_len: u32, @@ -351,37 +333,46 @@ pub struct BVHLightSampler { pub all_light_bounds: Bounds3f, } -unsafe impl Send for BVHLightSampler {} -unsafe impl Sync for BVHLightSampler {} - impl BVHLightSampler { + // Each array is paired with the length stored alongside it, so the slice can + // only be formed one way and indexing past the end is a bounds check rather + // than a silent read. These three are the only `unsafe` in the sampler. + + #[inline(always)] + fn nodes(&self) -> &[LightBVHNode] { + unsafe { self.nodes.as_slice(self.nodes_len as usize) } + } + + #[inline(always)] + fn infinite_lights(&self) -> &[LightIdx] { + unsafe { + self.infinite_lights + .as_slice(self.infinite_lights_len as usize) + } + } + + /// One bit trail per light, indexed by global light index. + #[inline(always)] + fn bit_trails(&self) -> &[u64] { + unsafe { self.bit_trails.as_slice(self.lights_len as usize) } + } + #[inline(always)] fn node(&self, idx: usize) -> &LightBVHNode { - unsafe { self.nodes.at(idx) } + &self.nodes()[idx] } #[inline(always)] - fn light(&self, idx: usize) -> Light { - unsafe { *self.lights.at(idx) } - } - - #[inline(always)] - fn infinite_light(&self, idx: usize) -> Light { - unsafe { *self.infinite_lights.at(idx) } + fn infinite_light(&self, idx: usize) -> LightIdx { + self.infinite_lights()[idx] } #[inline(always)] fn bit_trail(&self, idx: usize) -> u64 { - unsafe { *self.bit_trails.at(idx) } + self.bit_trails()[idx] } - #[inline(always)] - fn light_index_in(&self, base: Ptr, len: u32, light: &Light) -> Option { - let target = light as *const Light; - (0..len as usize).find(|&i| unsafe { base.add(i) }.as_raw() == target) - } - - fn evaluate_cost(&self, b: &LightBounds, bounds: &Bounds3f, dim: usize) -> Float { + pub fn evaluate_cost(b: &LightBounds, bounds: &Bounds3f, dim: usize) -> Float { let theta_o = b.cos_theta_o.acos(); let theta_e = b.cos_theta_e.acos(); let theta_w = (theta_o + theta_e).min(PI); @@ -404,13 +395,12 @@ impl LightSamplerTrait for BVHLightSampler { if u < p_inf { u /= p_inf; - // sample uniformly from infinite lights; their global index equals their - // position in the infinite_lights array (infinite lights are at 0..n_inf - // in the scene lights array by construction) - let ind = (u * inf_size).min(inf_size - 1.) as u32; + // Uniformly sample an infinite light and return its handle + // (`lightsamplers.h:277`: `infiniteLights[index]`). + let ind = ((u * inf_size) as usize).min(self.infinite_lights_len as usize - 1); let pmf = p_inf / inf_size; return Some(SampledLight { - light: LightIdx(ind), + light: self.infinite_light(ind), p: pmf, }); } @@ -462,18 +452,18 @@ impl LightSamplerTrait for BVHLightSampler { let empty_nodes = if self.nodes_len == 0 { 0. } else { 1. }; let n_infinite = self.infinite_lights_len as Float; - // Infinite lights occupy indices 0..infinite_lights_len in the global array - if idx.0 < self.infinite_lights_len { - return 1.0 / (n_infinite + empty_nodes); - } - let light_index = idx.0 as usize; if light_index >= self.lights_len as usize { return 0.0; } - // bit_trail[light_index] encodes the path from root to the leaf for this light + // bit_trail[light_index] encodes the path from root to this light's leaf. + // Canary value to check if no leaf. No leaf, it his infinite, or its power + // was zero let mut bit_trail = self.bit_trail(light_index); + if bit_trail == NO_BIT_TRAIL { + return 1.0 / (n_infinite + empty_nodes); + } let p_inf = n_infinite / (n_infinite + empty_nodes); let mut pmf = 1.0 - p_inf; let mut node_ind = 0; diff --git a/shared/src/lights/spot.rs b/shared/src/lights/spot.rs index 8cb44a9..62d48df 100644 --- a/shared/src/lights/spot.rs +++ b/shared/src/lights/spot.rs @@ -65,21 +65,6 @@ impl LightTrait for SpotLight { 0. } - fn l( - &self, - _p: Point3f, - _n: Normal3f, - _uv: Point2f, - _w: Vector3f, - _lambda: &SampledWavelengths, - ) -> SampledSpectrum { - todo!() - } - - fn le(&self, _ray: &Ray, _lambda: &SampledWavelengths) -> SampledSpectrum { - todo!() - } - #[cfg(not(target_os = "cuda"))] fn phi(&self, lambda: SampledWavelengths) -> SampledSpectrum { self.scale @@ -89,12 +74,8 @@ impl LightTrait for SpotLight { * ((1. - self.cos_falloff_start) + (self.cos_falloff_start - self.cos_falloff_end) / 2.) } - #[cfg(not(target_os = "cuda"))] - fn preprocess(&mut self, _scene_bounds: &Bounds3f) { - todo!() - } + fn preprocess(&mut self, _scene_bounds: &Bounds3f) {} - #[cfg(not(target_os = "cuda"))] fn bounds(&self) -> Option { let p = self .base diff --git a/shared/src/shapes/mesh.rs b/shared/src/shapes/mesh.rs index bad7c6b..fc136f2 100644 --- a/shared/src/shapes/mesh.rs +++ b/shared/src/shapes/mesh.rs @@ -31,10 +31,6 @@ pub struct BilinearPatchMesh { pub image_distribution: Ptr, } -unsafe impl Send for TriangleMesh {} -unsafe impl Sync for TriangleMesh {} -unsafe impl Send for BilinearPatchMesh {} -unsafe impl Sync for BilinearPatchMesh {} impl TriangleMesh { pub fn new( diff --git a/shared/src/spectra/colorspace.rs b/shared/src/spectra/colorspace.rs index 24b72ec..1c7e491 100644 --- a/shared/src/spectra/colorspace.rs +++ b/shared/src/spectra/colorspace.rs @@ -74,8 +74,6 @@ pub struct RGBColorSpace { pub rgb_to_spectrum_table: Ptr, } -unsafe impl Send for RGBColorSpace {} -unsafe impl Sync for RGBColorSpace {} impl RGBColorSpace { pub fn to_xyz(&self, rgb: RGB) -> XYZ { diff --git a/shared/src/spectra/simple.rs b/shared/src/spectra/simple.rs index c2223c7..16ff6b7 100644 --- a/shared/src/spectra/simple.rs +++ b/shared/src/spectra/simple.rs @@ -38,8 +38,6 @@ pub struct DenselySampledSpectrum { pub values: GVec, } -unsafe impl Send for DenselySampledSpectrum {} -unsafe impl Sync for DenselySampledSpectrum {} impl DenselySampledSpectrum { pub fn new(lambda_min: i32, lambda_max: i32, values: GVec) -> Self { @@ -262,8 +260,6 @@ impl PiecewiseLinearSpectrum { } } -unsafe impl Send for PiecewiseLinearSpectrum {} -unsafe impl Sync for PiecewiseLinearSpectrum {} impl SpectrumTrait for PiecewiseLinearSpectrum { fn evaluate(&self, lambda: Float) -> Float { diff --git a/shared/src/textures/marble.rs b/shared/src/textures/marble.rs index 4f4229c..87b878b 100644 --- a/shared/src/textures/marble.rs +++ b/shared/src/textures/marble.rs @@ -22,8 +22,6 @@ pub struct MarbleTexture { pub colorspace: Ptr, } -unsafe impl Send for MarbleTexture {} -unsafe impl Sync for MarbleTexture {} impl MarbleTexture { pub fn evaluate( diff --git a/shared/src/utils/alloc.rs b/shared/src/utils/alloc.rs index fe1a511..bf4fd49 100644 --- a/shared/src/utils/alloc.rs +++ b/shared/src/utils/alloc.rs @@ -17,7 +17,8 @@ unsafe impl Allocator for SystemAlloc { } unsafe fn deallocate(&self, ptr: NonNull, layout: Layout) { - Global.deallocate(ptr, layout) + // SAFETY: forwarded verbatim; caller upholds Allocator's contract. + unsafe { Global.deallocate(ptr, layout) } } unsafe fn grow( @@ -26,7 +27,8 @@ unsafe impl Allocator for SystemAlloc { old_layout: Layout, new_layout: Layout, ) -> Result, AllocError> { - Global.grow(ptr, old_layout, new_layout) + // SAFETY: forwarded verbatim; caller upholds Allocator's contract. + unsafe { Global.grow(ptr, old_layout, new_layout) } } unsafe fn shrink( @@ -35,7 +37,8 @@ unsafe impl Allocator for SystemAlloc { old_layout: Layout, new_layout: Layout, ) -> Result, AllocError> { - Global.shrink(ptr, old_layout, new_layout) + // SAFETY: forwarded verbatim; caller upholds Allocator's contract. + unsafe { Global.shrink(ptr, old_layout, new_layout) } } } diff --git a/shared/src/utils/sampling.rs b/shared/src/utils/sampling.rs index aaa6200..92295d1 100644 --- a/shared/src/utils/sampling.rs +++ b/shared/src/utils/sampling.rs @@ -712,8 +712,6 @@ pub struct PiecewiseConstant1D { pub func_integral: Float, } -unsafe impl Send for PiecewiseConstant1D {} -unsafe impl Sync for PiecewiseConstant1D {} impl PiecewiseConstant1D { pub fn new(f: &[Float]) -> Self { @@ -1101,8 +1099,6 @@ pub struct AliasTable { pub bins: GVec, } -unsafe impl Send for AliasTable {} -unsafe impl Sync for AliasTable {} impl AliasTable { pub fn new(weights: &[Float]) -> Self { diff --git a/shared/src/utils/soa.rs b/shared/src/utils/soa.rs index c07f813..196f48e 100644 --- a/shared/src/utils/soa.rs +++ b/shared/src/utils/soa.rs @@ -115,6 +115,7 @@ impl WorkQueue { i, self.size() ); - self.storage.get(i) + // SAFETY: bounds checked above; caller guarantees the slot is initialised. + unsafe { self.storage.get(i) } } } diff --git a/src/core/aggregates.rs b/src/core/aggregates.rs index e73c7c7..a13d391 100644 --- a/src/core/aggregates.rs +++ b/src/core/aggregates.rs @@ -375,12 +375,7 @@ impl CreateBVH for BVHAggregate { } let mut buckets = [Bucket::default(); N_BUCKETS]; let get_bucket_idx = |node: &BVHBuildNode| -> usize { - let offset = centroid_bounds.offset(&node.bounds().centroid())[dim]; - let mut b = (N_BUCKETS as Float * offset) as usize; - if b == N_BUCKETS { - b = N_BUCKETS - 1; - } - b + centroid_bounds.sah_bucket(&node.bounds().centroid(), dim, N_BUCKETS) }; // Initialize _Bucket_ for HLBVH SAH partition buckets @@ -535,11 +530,7 @@ fn build_recursive( 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; - } + let b = centroid_bounds.sah_bucket(&prim.centroid, dim, N_BUCKETS); buckets[b].count += 1; buckets[b].bounds = buckets[b].bounds.union(prim.bounds); } diff --git a/src/integrators/path.rs b/src/integrators/path.rs index 0d4745a..8adccf8 100644 --- a/src/integrators/path.rs +++ b/src/integrators/path.rs @@ -72,8 +72,6 @@ pub struct PathIntegrator { materials: Vec, } -unsafe impl Send for PathIntegrator {} -unsafe impl Sync for PathIntegrator {} impl PathIntegrator { pub fn new( diff --git a/src/lights/sampler.rs b/src/lights/sampler.rs index c6faa41..5ed3207 100644 --- a/src/lights/sampler.rs +++ b/src/lights/sampler.rs @@ -1,19 +1,23 @@ use crate::Arena; -use shared::core::light::{Light, LightTrait}; -use shared::lights::sampler::{LightSampler, PowerLightSampler, UniformLightSampler}; -use shared::spectra::{SampledSpectrum, SampledWavelengths}; -use shared::utils::sampling::AliasTable; -use shared::utils::Ptr; use shared::Float; +use shared::core::LightIdx; +use shared::core::geometry::Bounds3f; +use shared::core::light::LightBounds; +use shared::core::light::{Light, LightTrait}; +use shared::lights::sampler::{ + BVHLightSampler, CompactLightBounds, LightBVHNode, LightSampler, NO_BIT_TRAIL, + PowerLightSampler, UniformLightSampler, +}; +use shared::spectra::{SampledSpectrum, SampledWavelengths}; +use shared::utils::Ptr; +use shared::utils::partition_slice; +use shared::utils::sampling::AliasTable; pub fn create_light_sampler(name: &str, lights: &[Light], arena: &Arena) -> LightSampler { match name { "uniform" => LightSampler::Uniform(create_uniform(lights.len() as u32)), "power" => LightSampler::Power(create_power(lights, arena)), - "bvh" => { - log::warn!("BVH light sampler not yet implemented, falling back to power"); - LightSampler::Power(create_power(lights, arena)) - } + "bvh" => LightSampler::BVH(create_bvh(lights, arena)), _ => { log::error!("Unknown light sampler \"{}\", using power", name); LightSampler::Power(create_power(lights, arena)) @@ -28,7 +32,6 @@ fn create_uniform(lights_len: u32) -> UniformLightSampler { fn create_power(lights: &[Light], arena: &Arena) -> PowerLightSampler { if lights.is_empty() { return PowerLightSampler { - lights_len: 0, alias_table: Ptr::null(), }; } @@ -51,7 +54,142 @@ fn create_power(lights: &[Light], arena: &Arena) -> PowerLightSampler { let alias_ptr = arena.alloc(alias_table); PowerLightSampler { - lights_len: lights.len() as u32, alias_table: alias_ptr, } } + +// Straight up port of original +C+ BVHLightSampler::BVHLightSampler and ::buildBVH (lightsamplers.cpp). + +const N_BUCKETS: usize = 12; + +/// Accumulates the flat node array and per-light bit trails during the build. +struct BVHBuilder { + nodes: Vec, + bit_trails: Vec, + all_light_bounds: Bounds3f, +} + +impl BVHBuilder { + /// Builds the subtree over `lights`, pairs of `(global light index, bounds)`, + /// reordered in place, returning its node index and combined bounds. + fn build( + &mut self, + lights: &mut [(usize, LightBounds)], + bit_trail: u64, + depth: u32, + ) -> (usize, LightBounds) { + if lights.len() == 1 { + let (light_index, lb) = lights[0]; + let cb = CompactLightBounds::new(&lb, &self.all_light_bounds); + self.bit_trails[light_index] = bit_trail; + self.nodes + .push(LightBVHNode::make_leaf(light_index as u32, cb)); + return (self.nodes.len() - 1, lb); + } + + let mid = self.split(lights); + + let node_index = self.nodes.len(); + self.nodes.push(LightBVHNode::default()); + debug_assert!(depth < 64); + // Traversal assumes child0 sits at node_index + 1, so only child1 is stored. + let (child0, b0) = self.build(&mut lights[..mid], bit_trail, depth + 1); + debug_assert_eq!(child0, node_index + 1); + let (child1, b1) = self.build(&mut lights[mid..], bit_trail | (1 << depth), depth + 1); + + let lb = LightBounds::union(&b0, &b1); + let cb = CompactLightBounds::new(&lb, &self.all_light_bounds); + self.nodes[node_index] = LightBVHNode::make_interior(child1 as u32, cb); + (node_index, lb) + } + + /// Reorders `lights` at the SAH split point and returns where to cut. + fn split(&self, lights: &mut [(usize, LightBounds)]) -> usize { + let mut bounds = Bounds3f::default(); + let mut centroid_bounds = Bounds3f::default(); + for (_, lb) in lights.iter() { + bounds = bounds.union(lb.bounds); + centroid_bounds = centroid_bounds.union_point(lb.centroid()); + } + + let mut best: Option<(Float, usize, usize)> = None; // (cost, dim, bucket) + for dim in 0..3 { + if centroid_bounds.p_max[dim] == centroid_bounds.p_min[dim] { + continue; + } + + let mut buckets = [LightBounds::default(); N_BUCKETS]; + for (_, lb) in lights.iter() { + let b = centroid_bounds.sah_bucket(&lb.centroid(), dim, N_BUCKETS); + buckets[b] = LightBounds::union(&buckets[b], lb); + } + + let union = |bs: &[LightBounds]| { + bs.iter() + .fold(LightBounds::default(), |a, b| LightBounds::union(&a, b)) + }; + // pbrt only considers splits after buckets 1..N_BUCKETS-1. + for b in 1..N_BUCKETS - 1 { + let cost = BVHLightSampler::evaluate_cost(&union(&buckets[..=b]), &bounds, dim) + + BVHLightSampler::evaluate_cost(&union(&buckets[b + 1..]), &bounds, dim); + if cost > 0. && best.map_or(true, |(c, ..)| cost < c) { + best = Some((cost, dim, b)); + } + } + } + + let Some((_, dim, bucket)) = best else { + return lights.len() / 2; + }; + let mid = partition_slice(lights, |(_, lb)| { + centroid_bounds.sah_bucket(&lb.centroid(), dim, N_BUCKETS) <= bucket + }); + if mid == 0 || mid == lights.len() { + lights.len() / 2 + } else { + mid + } + } +} + +fn create_bvh(lights: &[Light], arena: &Arena) -> BVHLightSampler { + // Infinite lights are sampled uniformly; bounded lights that emit go in the BVH. + let mut infinite_lights = Vec::new(); + let mut bvh_lights = Vec::new(); + let mut all_light_bounds = Bounds3f::default(); + + for (i, light) in lights.iter().enumerate() { + match light.bounds() { + None => infinite_lights.push(LightIdx(i as u32)), + Some(lb) if lb.phi > 0. => { + all_light_bounds = all_light_bounds.union(lb.bounds); + bvh_lights.push((i, lb)); + } + Some(_) => {} + } + } + + let mut builder = BVHBuilder { + nodes: Vec::new(), + // Lights that never become a leaf keep the sentinel. + bit_trails: vec![NO_BIT_TRAIL; lights.len()], + all_light_bounds, + }; + if !bvh_lights.is_empty() { + builder.build(&mut bvh_lights, 0, 0); + } + + let (nodes, nodes_len) = arena.alloc_slice(&builder.nodes); + let (bit_trails, _) = arena.alloc_slice(&builder.bit_trails); + let (infinite, infinite_len) = arena.alloc_slice(&infinite_lights); + + BVHLightSampler { + nodes, + infinite_lights: infinite, + bit_trails, + nodes_len: nodes_len as u32, + lights_len: lights.len() as u32, + infinite_lights_len: infinite_len as u32, + all_light_bounds, + } +}