From 5ff804415861ab6acc4401c1f428b916a5734a3d Mon Sep 17 00:00:00 2001 From: Wito Wiala Date: Thu, 28 May 2026 14:40:45 +0100 Subject: [PATCH] Cleaning unused imports, refactoring wavefront integration --- shared/src/core/sampler.rs | 2 +- shared/src/core/texture.rs | 61 +++- shared/src/lib.rs | 2 +- shared/src/shapes/triangle.rs | 3 +- shared/src/utils/mod.rs | 2 +- shared/src/utils/options.rs | 4 +- shared/src/wavefront/aggregate.rs | 185 +---------- shared/src/wavefront/integrator.rs | 469 +-------------------------- shared/src/wavefront/mod.rs | 1 + src/core/aggregates.rs | 4 +- src/core/camera.rs | 52 ++- src/core/color.rs | 3 +- src/core/film.rs | 14 +- src/core/filter.rs | 4 +- src/core/image/io.rs | 2 +- src/core/image/mod.rs | 10 +- src/core/image/ops.rs | 3 +- src/core/interaction.rs | 6 +- src/core/light.rs | 4 +- src/core/render.rs | 56 ++-- src/core/scene/builder.rs | 4 +- src/core/scene/scene.rs | 123 ++++++- src/films/gbuffer.rs | 6 +- src/films/mod.rs | 3 - src/films/rgb.rs | 3 +- src/films/spectral.rs | 3 +- src/integrators/mod.rs | 3 +- src/integrators/path.rs | 7 +- src/integrators/pipeline.rs | 11 +- src/integrators/state.rs | 6 + src/lib.rs | 2 + src/lights/distant.rs | 2 +- src/lights/goniometric.rs | 7 +- src/lights/infinite.rs | 8 +- src/lights/point.rs | 2 +- src/lights/sampler.rs | 2 +- src/lights/spot.rs | 2 +- src/samplers/halton.rs | 2 +- src/shapes/bilinear.rs | 2 +- src/shapes/curves.rs | 4 +- src/shapes/mesh.rs | 11 +- src/shapes/triangle.rs | 2 +- src/spectra/colorspace.rs | 1 - src/spectra/data.rs | 2 +- src/spectra/mod.rs | 8 +- src/utils/containers.rs | 10 +- src/utils/mipmap.rs | 2 +- src/utils/parallel.rs | 1 - src/utils/parameters.rs | 12 +- src/utils/upload.rs | 5 +- src/wavefront/aggregate.rs | 181 +++++++++++ src/wavefront/integrator.rs | 497 ++++++++++++++++++++++++++--- src/wavefront/mod.rs | 3 + 53 files changed, 973 insertions(+), 851 deletions(-) create mode 100644 src/wavefront/aggregate.rs diff --git a/shared/src/core/sampler.rs b/shared/src/core/sampler.rs index 2ed1840..a388de4 100644 --- a/shared/src/core/sampler.rs +++ b/shared/src/core/sampler.rs @@ -193,7 +193,7 @@ impl SamplerTrait for HaltonSampler { } fn get1d(&mut self) -> Float { - if self.dim > PRIME_TABLE_SIZE as u32 { + if self.dim >= PRIME_TABLE_SIZE as u32 { self.dim = 2; } self.sample_dimension(self.dim) diff --git a/shared/src/core/texture.rs b/shared/src/core/texture.rs index fc3d3dd..8b5574d 100644 --- a/shared/src/core/texture.rs +++ b/shared/src/core/texture.rs @@ -1,6 +1,6 @@ use crate::core::color::ColorEncoding; use crate::core::geometry::{ - Normal3f, Point2f, Point3f, Vector2f, Vector3f, VectorLike, spherical_phi, spherical_theta, + spherical_phi, spherical_theta, Normal3f, Point2f, Point3f, Vector2f, Vector3f, VectorLike, }; use crate::core::image::WrapMode; use crate::core::interaction::{Interaction, InteractionTrait, SurfaceInteraction}; @@ -9,9 +9,9 @@ use crate::spectra::{ SampledWavelengths, }; +use crate::utils::math::square; use crate::utils::Ptr; use crate::utils::Transform; -use crate::utils::math::square; use crate::{Float, INV_2_PI, INV_PI, PI}; use enum_dispatch::enum_dispatch; use num_traits::Float as NumFloat; @@ -393,7 +393,6 @@ pub enum GPUSpectrumTexture { Dots(SpectrumDotsTexture), Scaled(GPUSpectrumScaledTexture), Image(GPUSpectrumImageTexture), - Ptex(GPUSpectrumPtexTexture), Mix(GPUSpectrumMixTexture), } @@ -411,7 +410,6 @@ impl GPUSpectrumTexture { GPUSpectrumTexture::DirectionMix(t) => t.evaluate(ctx, lambda), GPUSpectrumTexture::Dots(t) => t.evaluate(ctx, lambda), GPUSpectrumTexture::Scaled(t) => t.evaluate(ctx, lambda), - GPUSpectrumTexture::Ptex(t) => t.evaluate(ctx, lambda), GPUSpectrumTexture::Image(t) => t.evaluate(ctx, lambda), GPUSpectrumTexture::Mix(t) => t.evaluate(ctx, lambda), } @@ -460,3 +458,58 @@ impl TextureEvaluator for UniversalTextureEvaluator { true } } + +#[repr(C)] +#[derive(Copy, Clone, Default)] +pub struct BasicTextureEvaluator; + +impl TextureEvaluator for BasicTextureEvaluator { + fn evaluate_float(&self, tex: &GPUFloatTexture, ctx: &TextureEvalContext) -> Float { + match tex { + GPUFloatTexture::Constant(t) => t.evaluate(ctx), + GPUFloatTexture::Image(t) => t.evaluate(ctx), + _ => 0.0, + } + } + + fn evaluate_spectrum( + &self, + tex: &GPUSpectrumTexture, + ctx: &TextureEvalContext, + lambda: &SampledWavelengths, + ) -> SampledSpectrum { + match tex { + GPUSpectrumTexture::Constant(t) => t.evaluate(ctx, lambda), + GPUSpectrumTexture::Image(t) => t.evaluate(ctx, lambda), + _ => SampledSpectrum::new(0.0), + } + } + + fn can_evaluate( + &self, + ftex: &[Ptr], + stex: &[Ptr], + ) -> bool { + for t in ftex { + if t.is_null() { + continue; + } + match t.get().unwrap() { + GPUFloatTexture::Constant(_) + | GPUFloatTexture::Image(_) => {} + _ => return false, + } + } + for t in stex { + if t.is_null() { + continue; + } + match t.get().unwrap() { + GPUSpectrumTexture::Constant(_) + | GPUSpectrumTexture::Image(_) => {} + _ => return false, + } + } + true + } +} diff --git a/shared/src/lib.rs b/shared/src/lib.rs index db60950..73c4fdf 100644 --- a/shared/src/lib.rs +++ b/shared/src/lib.rs @@ -20,6 +20,6 @@ pub mod wavefront; pub use core::pbrt::*; pub use utils::alloc::{gbox, gvec, gvec_from_slice, gvec_with_capacity, leak, GBox, GVec}; -pub use utils::{Array2D, PBRTOptions, Ptr, Transform}; +pub use utils::{Array2D, BasicPBRTOptions, PBRTOptions, Ptr, Transform}; pub use utils::soa::WorkQueue; pub use wavefront::{WavefrontAggregate}; diff --git a/shared/src/shapes/triangle.rs b/shared/src/shapes/triangle.rs index 95f6fdc..0d55daf 100644 --- a/shared/src/shapes/triangle.rs +++ b/shared/src/shapes/triangle.rs @@ -42,8 +42,9 @@ impl TriangleShape { pub const MAX_SPHERICAL_SAMPLE_AREA: Float = 6.22; fn mesh(&self) -> &TriangleMesh { - &*self.mesh + self.mesh.get().unwrap() } + fn get_vertex_indices(&self) -> [usize; 3] { let mesh = self.mesh(); let base = (self.tri_index as usize) * 3; diff --git a/shared/src/utils/mod.rs b/shared/src/utils/mod.rs index 165e2d2..7d76a4f 100644 --- a/shared/src/utils/mod.rs +++ b/shared/src/utils/mod.rs @@ -18,7 +18,7 @@ pub mod transform; pub use atomic::{AtomicFloat, AtomicU32}; pub use containers::Array2D; -pub use options::PBRTOptions; +pub use options::{BasicPBRTOptions, PBRTOptions}; pub use ptr::Ptr; pub use transform::{AnimatedTransform, Transform, TransformGeneric}; diff --git a/shared/src/utils/options.rs b/shared/src/utils/options.rs index d71b90b..334bd6f 100644 --- a/shared/src/utils/options.rs +++ b/shared/src/utils/options.rs @@ -9,7 +9,7 @@ pub enum RenderingCoordinateSystem { World, } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Copy)] pub struct BasicPBRTOptions { pub seed: i32, pub quiet: bool, @@ -42,7 +42,7 @@ impl Default for BasicPBRTOptions { } } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Copy)] pub struct PBRTOptions { pub basic: BasicPBRTOptions, diff --git a/shared/src/wavefront/aggregate.rs b/shared/src/wavefront/aggregate.rs index ba9406b..6fbd618 100644 --- a/shared/src/wavefront/aggregate.rs +++ b/shared/src/wavefront/aggregate.rs @@ -1,9 +1,5 @@ -use crate::core::geometry::{Bounds3f, Ray, Vector3f}; -use crate::core::interaction::InteractionTrait; -use crate::core::material::MaterialTrait; -use crate::core::primitive::{Primitive, PrimitiveTrait}; -use crate::core::texture::{TextureEvaluator, UniversalTextureEvaluator}; use crate::wavefront::workitems::*; +use crate::core::geometry::Bounds3f; pub trait WavefrontAggregate { fn bounds(&self) -> Bounds3f; @@ -33,184 +29,5 @@ pub trait WavefrontAggregate { shadow_ray_q: &ShadowRayQueue, pixel_sample_state: &PixelSampleState, ); - - // fn intersect_one_random( - // &self, - // max_rays: usize, - // subsurface_scatte_q: &mut SubsurfaceScatterQueue, - // ) { - // todo!() - // } } -pub struct CpuAggregate { - pub aggregate: Primitive, -} - -impl CpuAggregate { - pub fn new(aggregate: Primitive) -> Self { - Self { aggregate } - } -} - -impl WavefrontAggregate for CpuAggregate { - fn bounds(&self) -> Bounds3f { - self.aggregate.bounds() - } - - fn intersect_closest( - &self, - max_rays: usize, - ray_q: &RayQueue, - escaped_ray_q: &EscapedRayQueue, - hit_area_light_q: &HitAreaLightQueue, - basic_eval_mtl_q: &MaterialEvalQueue, - universal_eval_mtl_q: &MaterialEvalQueue, - next_ray_q: &RayQueue, - pixel_sample_state: &PixelSampleState, - ) { - let n_rays = ray_q.size().min(max_rays as u32); - - for i in 0..n_rays as usize { - let work = unsafe { ray_q.get(i) }; - - let ray = Ray::new(work.ray_o, work.ray_d, Some(work.ray_time), work.ray_medium); - - // Read path state from PixelSampleState - let pi = work.pixel_index as usize; - let beta = pixel_sample_state.beta.get(pi); - let r_u = pixel_sample_state.r_u.get(pi); - let r_l = pixel_sample_state.r_l.get(pi); - let lambda = pixel_sample_state.lambda.get(pi); - let depth = pixel_sample_state.depth.get(pi); - let specular_bounce = pixel_sample_state.specular_bounce.get(pi) != 0; - let any_non_specular = pixel_sample_state.any_non_specular_bounces.get(pi) != 0; - let eta_scale = pixel_sample_state.eta_scale.get(pi); - let prev_intr_ctx = pixel_sample_state.prev_intr_ctx.get(pi); - - let Some(si) = self.aggregate.intersect(&ray, None) else { - // Ray escaped — push to escaped ray queue - escaped_ray_q.push(EscapedRayWorkItem { - ray_o: work.ray_o, - ray_d: work.ray_d, - lambda, - pixel_index: work.pixel_index, - beta, - r_u, - r_l, - depth, - specular_bounce, - prev_intr_ctx, - }); - continue; - }; - - let intr = &si.intr; - - // Check for null material (medium interface) — re-queue the ray - if intr.material.is_null() { - // Skip intersection and continue ray - // TODO: offset ray origin past the intersection - next_ray_q.push(RayWorkItem { - ray_o: intr.p(), - ray_d: work.ray_d, - ray_time: work.ray_time, - ray_medium: work.ray_medium, - has_differentials: work.has_differentials, - differential: work.differential, - pixel_index: work.pixel_index, - }); - continue; - } - - // Check for area light hit - if !intr.area_light.is_null() { - hit_area_light_q.push(HitAreaLightWorkItem { - area_light: intr.area_light, - p: intr.p(), - n: intr.n(), - uv: intr.common.uv, - wo: -work.ray_d, - lambda, - pixel_index: work.pixel_index, - beta, - r_u, - r_l, - depth, - specular_bounce, - prev_intr_ctx, - }); - } - - // Determine which material evaluation queue to use based on - // whether the material's textures can be evaluated with the - // basic evaluator (cheaper) or need the universal one. - let material = *intr.material.get().unwrap(); - let eval_q = if material.can_evaluate_textures(&UniversalTextureEvaluator) { - basic_eval_mtl_q - } else { - universal_eval_mtl_q - }; - - eval_q.push(MaterialEvalWorkItem { - p: intr.p(), - n: intr.n(), - ns: intr.shading.n, - dpdu: intr.shading.dpdu, - dpdv: intr.shading.dpdv, - uv: intr.common.uv, - wo: -work.ray_d, - time: work.ray_time, - face_index: intr.face_index, - material: intr.material, - area_light: intr.area_light, - medium_interface: intr.common.medium_interface, - pixel_index: work.pixel_index, - lambda, - beta, - r_u, - any_non_specular_bounces: any_non_specular, - depth, - eta_scale, - }); - } - } - - fn intersect_shadow( - &self, - max_rays: usize, - shadow_ray_q: &ShadowRayQueue, - pixel_sample_state: &PixelSampleState, - ) { - let n_rays = shadow_ray_q.size().min(max_rays as u32); - - for i in 0..n_rays as usize { - let work = unsafe { shadow_ray_q.get(i) }; - - let ray = Ray::new( - work.ray_o, - work.ray_d, - Some(work.ray_time), - crate::Ptr::null(), - ); - - // If the shadow ray is NOT occluded, add the direct lighting - // contribution to the pixel's accumulated radiance. - if !self.aggregate.intersect_p(&ray, Some(work.t_max)) { - let pi = work.pixel_index as usize; - let mut l = pixel_sample_state.l.get(pi); - l += work.l_d; - pixel_sample_state.l.set(pi, l); - } - } - } - - fn intersect_shadow_tr( - &self, - max_rays: usize, - shadow_ray_q: &ShadowRayQueue, - pixel_sample_state: &PixelSampleState, - ) { - self.intersect_shadow(max_rays, shadow_ray_q, pixel_sample_state); - } -} diff --git a/shared/src/wavefront/integrator.rs b/shared/src/wavefront/integrator.rs index d4274c5..e272810 100644 --- a/shared/src/wavefront/integrator.rs +++ b/shared/src/wavefront/integrator.rs @@ -1,22 +1,9 @@ -use crate::core::bxdf::FArgs; -use crate::core::bxdf::TransportMode; -use crate::core::camera::{Camera, CameraTrait}; +use crate::core::camera::Camera; use crate::core::film::Film; -use crate::core::filter::{Filter, FilterTrait}; -use crate::core::geometry::{ - Bounds2i, Point2f, Point2i, Point3f, Point3fi, Ray, RayDifferential, Vector2f, Vector3f, - VectorLike, -}; -use crate::core::interaction::InteractionTrait; -use crate::core::light::{Light, LightSampleContext, LightTrait}; -use crate::core::material::{MaterialEvalContext, MaterialTrait}; -use crate::core::sampler::{CameraSample, Sampler, SamplerTrait}; -use crate::core::texture::{TextureEvalContext, UniversalTextureEvaluator}; -use crate::lights::sampler::{LightSampler, LightSamplerTrait}; -use crate::spectra::{SampledSpectrum, SampledWavelengths}; -use crate::utils::math::square; -use crate::utils::sampling::power_heuristic; -use crate::utils::soa::{SoA, SoAAllocator, WorkQueue}; +use crate::core::filter::Filter; +use crate::core::light::Light; +use crate::core::sampler::Sampler; +use crate::lights::sampler::LightSampler; use crate::wavefront::aggregate::WavefrontAggregate; use crate::wavefront::workitems::*; use crate::{Float, GVec, Ptr}; @@ -24,460 +11,26 @@ use crate::{Float, GVec, Ptr}; pub struct WavefrontPathIntegrator { pub aggregate: A, pub camera: Camera, - pub film: Film, - pub filter: Filter, pub sampler: Sampler, pub max_depth: u32, pub samples_per_pixel: u32, pub regularize: bool, - - // Lights pub infinite_lights: GVec>, - - // Queue capacity = resolution.x * scanlines_per_pass pub max_queue_size: u32, pub scanlines_per_pass: u32, - + pub light_sampler: LightSampler, + pub film: Ptr, + pub filter: Ptr, pub ray_queues: [RayQueue; 2], pub shadow_ray_queue: ShadowRayQueue, pub escaped_ray_queue: EscapedRayQueue, pub hit_area_light_queue: HitAreaLightQueue, pub basic_eval_material_queue: MaterialEvalQueue, pub universal_eval_material_queue: MaterialEvalQueue, - pub light_sampler: LightSampler, - - // Persistent per-path state pub pixel_sample_state: PixelSampleState, } -impl WavefrontPathIntegrator { - pub fn render(&mut self) { - let pixel_bounds = self.film.pixel_bounds(); - let resolution = pixel_bounds.diagonal(); - - for sample_index in 0..self.samples_per_pixel { - // Process image in scanline batches - let mut y0 = pixel_bounds.p_min.y(); - while y0 < pixel_bounds.p_max.y() { - let y1 = (y0 + self.scanlines_per_pass as i32).min(pixel_bounds.p_max.y()); - - // Reset the primary ray queue for this set - self.ray_queues[0].reset(); - - self.generate_camera_rays(y0, y1, sample_index, &pixel_bounds); - - for depth in 0..=self.max_depth { - let current = (depth % 2) as usize; - let next = ((depth + 1) % 2) as usize; - - // Reset output queues before intersection - self.ray_queues[next].reset(); - self.escaped_ray_queue.reset(); - self.hit_area_light_queue.reset(); - self.basic_eval_material_queue.reset(); - self.universal_eval_material_queue.reset(); - self.shadow_ray_queue.reset(); - - // Skip if no rays to trace - if self.ray_queues[current].size() == 0 { - break; - } - - // Sorting of rays into output queues - self.aggregate.intersect_closest( - self.max_queue_size as usize, - &self.ray_queues[current], - &self.escaped_ray_queue, - &self.hit_area_light_queue, - &self.basic_eval_material_queue, - &self.universal_eval_material_queue, - &self.ray_queues[next], - &self.pixel_sample_state, - ); - - // Infinite light contributions - self.handle_escaped_rays(); - - // Area light contributions - self.handle_emissive_intersections(); - - // Last depth — don't evaluate materials or sample lights - if depth == self.max_depth { - break; - } - - // Evaluate materials, sample BSDFs, sample direct lighting - // This pushes to shadow_ray_queue and ray_queues[next] - self.evaluate_materials_and_bsdfs(depth); - - // Add direct lighting to pixels - self.aggregate.intersect_shadow( - self.max_queue_size as usize, - &self.shadow_ray_queue, - &self.pixel_sample_state, - ); - } - - // Update film from accumulated pixel sample state - self.update_film(y0, y1, &pixel_bounds); - - y0 = y1; - } - } - } - - /// Stage 1: Generate camera rays for scanlines [y0, y1). - fn generate_camera_rays( - &mut self, - y0: i32, - y1: i32, - sample_index: u32, - pixel_bounds: &Bounds2i, - ) { - // For each pixel in the scanline range, generate a camera ray - // and push it to the ray queue. Also initialize the PixelSampleState. - for y in y0..y1 { - for x in pixel_bounds.p_min.x()..pixel_bounds.p_max.x() { - let p_pixel = Point2i::new(x, y); - - // TODO: proper sampler state per pixel/sample - // For now, use a simple approach - self.sampler - .start_pixel_sample(p_pixel, sample_index as i32, Some(0)); - - let lambda = SampledWavelengths::sample_visible(self.sampler.get1d()); - - let camera_sample = crate::core::sampler::get_camera_sample( - &mut self.sampler, - p_pixel, - &self.filter, - ); - - let Some(camera_ray) = self.camera.generate_ray(camera_sample, &lambda) else { - continue; - }; - - // Compute pixel index for this sample - let pixel_index = self.ray_queues[0].size(); - - // Initialize persistent pixel state - let pi = pixel_index as usize; - self.pixel_sample_state.l.set(pi, SampledSpectrum::new(0.0)); - self.pixel_sample_state.beta.set(pi, camera_ray.weight); - self.pixel_sample_state.lambda.set(pi, lambda); - self.pixel_sample_state - .r_u - .set(pi, SampledSpectrum::new(1.0)); - self.pixel_sample_state - .r_l - .set(pi, SampledSpectrum::new(1.0)); - self.pixel_sample_state.depth.set(pi, 0); - self.pixel_sample_state.specular_bounce.set(pi, 1); - self.pixel_sample_state.any_non_specular_bounces.set(pi, 0); - self.pixel_sample_state.eta_scale.set(pi, 1.0); - self.pixel_sample_state.p_film.set(pi, camera_sample.p_film); - self.pixel_sample_state - .filter_weight - .set(pi, camera_sample.filter_weight); - self.pixel_sample_state - .prev_intr_ctx - .set(pi, LightSampleContext::default()); - - // Push ray to queue - self.ray_queues[0].push(RayWorkItem { - ray_o: camera_ray.ray.o, - ray_d: camera_ray.ray.d, - ray_time: camera_ray.ray.time, - ray_medium: camera_ray.ray.medium, - pixel_index: pixel_index, - has_differentials: true, - differential: RayDifferential::default(), - }); - } - } - } - - /// Handle escaped rays — evaluate infinite lights. - fn handle_escaped_rays(&self) { - let n = self.escaped_ray_queue.size(); - for i in 0..n as usize { - let w = unsafe { self.escaped_ray_queue.storage.get(i) }; - - let mut l_contrib = SampledSpectrum::new(0.0); - - // Evaluate all infinite lights - for light_ptr in &self.infinite_lights { - let light = light_ptr.get().unwrap(); - let ray = crate::core::geometry::Ray::new(w.ray_o, w.ray_d, None, Ptr::null()); - let le = light.le(&ray, &w.lambda); - if le.is_black() { - continue; - } - - if w.depth == 0 || w.specular_bounce { - // No MIS for direct camera rays or specular bounces - l_contrib += w.beta * le / w.r_u.average(); - } else { - // MIS with light sampling - // TODO: compute light PDF for MIS weight - // For now, use unidirectional weight only - l_contrib += w.beta * le / w.r_u.average(); - } - } - - if !l_contrib.is_black() { - let pi = w.pixel_index as usize; - let mut l = self.pixel_sample_state.l.get(pi); - l += l_contrib; - self.pixel_sample_state.l.set(pi, l); - } - } - } - - /// Handle emissive intersections — area light contribution with MIS. - fn handle_emissive_intersections(&self) { - let n = self.hit_area_light_queue.size(); - for i in 0..n as usize { - let w = unsafe { self.hit_area_light_queue.storage.get(i) }; - - let light = w.area_light.get().unwrap(); - let le = light.l(w.p, w.n, w.uv, w.wo, &w.lambda); - if le.is_black() { - continue; - } - - let l_contrib = if w.depth == 0 || w.specular_bounce { - w.beta * le / w.r_u.average() - } else { - // MIS: combine BSDF and light sampling weights - // TODO: full MIS with light sampler PDF - w.beta * le / w.r_u.average() - }; - - if !l_contrib.is_black() { - let pi = w.pixel_index as usize; - let mut l = self.pixel_sample_state.l.get(pi); - l += l_contrib; - self.pixel_sample_state.l.set(pi, l); - } - } - } - - fn evaluate_materials_and_bsdfs(&mut self, depth: u32) { - self.evaluate_material_queue_impl(depth, false); - self.evaluate_material_queue_impl(depth, true); - } - - fn evaluate_material_queue_impl(&mut self, depth: u32, use_universal: bool) { - let queue = if use_universal { - &self.universal_eval_material_queue - } else { - &self.basic_eval_material_queue - }; - - let n = queue.size(); - let next = ((depth + 1) % 2) as usize; - - for i in 0..n as usize { - let w = unsafe { queue.storage.get(i) }; - let pi = w.pixel_index as usize; - - let lambda = self.pixel_sample_state.lambda.get(pi); - let beta = self.pixel_sample_state.beta.get(pi); - let any_non_specular = self.pixel_sample_state.any_non_specular_bounces.get(pi) != 0; - let eta_scale = self.pixel_sample_state.eta_scale.get(pi); - - let Some(material) = w.material.get() else { - continue; - }; - - let tex_eval = UniversalTextureEvaluator; - let ctx = MaterialEvalContext { - texture: TextureEvalContext { - p: w.p, - dpdx: Vector3f::zero(), - dpdy: Vector3f::zero(), - n: w.n, - uv: w.uv, - dudx: 0.0, - dudy: 0.0, - dvdx: 0.0, - dvdy: 0.0, - face_index: w.face_index, - }, - wo: w.wo, - ns: w.ns, - dpdus: w.dpdu, - }; - let mut bsdf = material.get_bsdf(&tex_eval, &ctx, &lambda); - - if bsdf.flags().is_empty() { - continue; - } - - if self.regularize && any_non_specular { - bsdf.regularize(); - } - - if depth >= self.max_depth { - continue; - } - - // Sample a light, compute contribution, - // push shadow ray with deferred visibility - if bsdf.flags().is_non_specular() { - let light_ctx = LightSampleContext { - pi: Point3fi::new_from_point(w.p), - n: w.n, - ns: w.ns, - }; - - if let Some(sampled_light) = self - .light_sampler - .sample_with_context(&light_ctx, self.sampler.get1d()) - { - if let Some(ls) = sampled_light.light.sample_li( - &light_ctx, - self.sampler.get2d(), - &lambda, - true, - ) { - if !ls.l.is_black() && ls.pdf > 0.0 { - let wi = ls.wi; - if let Some(f_val) = bsdf.f(w.wo, wi, TransportMode::Radiance) { - let f_cos = f_val * wi.abs_dot(w.ns.into()); - if !f_cos.is_black() { - let p_l = sampled_light.p * ls.pdf; - let l_d = if sampled_light.light.light_type().is_delta_light() { - beta * ls.l * f_cos / p_l - } else { - let p_b = bsdf.pdf(w.wo, wi, FArgs::default()); - let w_l = power_heuristic(1, p_l, 1, p_b); - beta * w_l * ls.l * f_cos / p_l - }; - - if !l_d.is_black() { - let ray_o = Ray::offset_origin( - &Point3fi::new_from_point(w.p), - &w.n, - &wi, - ); - let t_max = (1.0 - 1e-4) - * (Point3f::from(ls.p_light.p()) - ray_o).norm() - / wi.norm(); - - self.shadow_ray_queue.push(ShadowRayWorkItem { - ray_o, - ray_d: wi, - ray_time: w.time, - t_max, - lambda, - l_d, - pixel_index: w.pixel_index, - }); - } - } - } - } - } - } - } - - // Sample BSDF for next bounce - let wo = w.wo; - let Some(bs) = bsdf.sample_f( - wo, - self.sampler.get1d(), - self.sampler.get2d(), - FArgs::default(), - ) else { - continue; - }; - - let f_cos = bs.f * bs.wi.abs_dot(w.ns.into()); - if f_cos.is_black() || bs.pdf == 0.0 { - continue; - } - let new_beta = beta * f_cos / bs.pdf; - - let new_depth = depth + 1; - - // Russian roulette - if new_depth > 3 { - let rr_beta = new_beta.max_component_value(); - if rr_beta < 0.25 { - let q = (1.0 - rr_beta).max(0.0_f32); - if self.sampler.get1d() < q { - continue; - } - } - } - - let ray_o = Ray::offset_origin(&Point3fi::new_from_point(w.p), &w.n, &bs.wi); - - // Update PixelSampleState - self.pixel_sample_state.beta.set(pi, new_beta); - self.pixel_sample_state.depth.set(pi, new_depth); - self.pixel_sample_state - .specular_bounce - .set(pi, bs.is_specular() as u8); - self.pixel_sample_state - .any_non_specular_bounces - .set(pi, (any_non_specular || !bs.is_specular()) as u8); - self.pixel_sample_state.eta_scale.set( - pi, - if bs.is_transmissive() { - eta_scale * square(bs.eta) - } else { - eta_scale - }, - ); - self.pixel_sample_state.prev_intr_ctx.set( - pi, - LightSampleContext { - pi: Point3fi::new_from_point(w.p), - n: w.n, - ns: w.ns, - }, - ); - - // Push next bounce ray - self.ray_queues[next].push(RayWorkItem { - ray_o, - ray_d: bs.wi, - ray_time: w.time, - ray_medium: Ptr::null(), - pixel_index: w.pixel_index, - has_differentials: true, - differential: RayDifferential::default(), - }); - } - } - - /// Update film — write accumulated radiance to film pixels. - fn update_film(&self, y0: i32, y1: i32, pixel_bounds: &Bounds2i) { - // The pixel_sample_state indices map to rays generated in - // generate_camera_rays. We need to walk the same pixel order - // and read back the accumulated L values. - let mut pi = 0usize; - for y in y0..y1 { - for x in pixel_bounds.p_min.x()..pixel_bounds.p_max.x() { - let l = self.pixel_sample_state.l.get(pi); - let lambda = self.pixel_sample_state.lambda.get(pi); - let filter_weight = self.pixel_sample_state.filter_weight.get(pi); - let p_film = self.pixel_sample_state.p_film.get(pi); - - // Add sample to film - self.film.add_sample( - Point2i::new(p_film.x() as i32, p_film.y() as i32), - l, - &lambda, - Some(&crate::core::film::VisibleSurface::default()), - filter_weight, - ); - - pi += 1; - } - } - } +pub trait WavefrontRenderer { + fn render(&mut self); } + diff --git a/shared/src/wavefront/mod.rs b/shared/src/wavefront/mod.rs index c1c76d6..a2590f3 100644 --- a/shared/src/wavefront/mod.rs +++ b/shared/src/wavefront/mod.rs @@ -4,4 +4,5 @@ pub mod integrator; pub use workitems::*; pub use aggregate::WavefrontAggregate; +pub use integrator::{WavefrontPathIntegrator, WavefrontRenderer}; diff --git a/src/core/aggregates.rs b/src/core/aggregates.rs index 9eaa911..e73c7c7 100644 --- a/src/core/aggregates.rs +++ b/src/core/aggregates.rs @@ -1,9 +1,7 @@ -use crate::Arena; use rayon::prelude::*; use shared::core::aggregates::{BVHAggregate, LinearBVHNode, SplitMethod}; -use shared::core::geometry::{Bounds3f, Point3f, Ray, Vector3f}; +use shared::core::geometry::{Bounds3f, Point3f}; 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, gvec_from_slice, Float}; diff --git a/src/core/camera.rs b/src/core/camera.rs index 5de799e..8423a1d 100644 --- a/src/core/camera.rs +++ b/src/core/camera.rs @@ -44,7 +44,7 @@ impl CameraBaseParameters { } Ok(CameraBaseParameters { - camera_transform: camera_transform.clone(), + camera_transform: *camera_transform, shutter_open, shutter_close, film, @@ -56,7 +56,7 @@ impl CameraBaseParameters { pub trait CameraBaseFactory { fn create(p: CameraBaseParameters) -> CameraBase { CameraBase { - camera_transform: p.camera_transform.clone(), + camera_transform: p.camera_transform, shutter_open: p.shutter_open, shutter_close: p.shutter_close, film: Ptr::from(p.film.clone().as_ref()), @@ -144,18 +144,16 @@ impl CameraFactory for Camera { if !sw.is_empty() { if get_options().fullscreen { eprint!("Screenwindow is ignored in fullscreen mode"); + } else if sw.len() == 4 { + screen = Bounds2f::from_points( + Point2f::new(sw[0], sw[2]), + Point2f::new(sw[1], sw[3]), + ); } else { - if sw.len() == 4 { - screen = Bounds2f::from_points( - Point2f::new(sw[0], sw[2]), - Point2f::new(sw[1], sw[3]), - ); - } else { - return Err(anyhow!( - "{}: screenwindow param must have four values", - loc - )); - } + return Err(anyhow!( + "{}: screenwindow param must have four values", + loc + )); } } @@ -191,18 +189,16 @@ impl CameraFactory for Camera { if !sw.is_empty() { if get_options().fullscreen { eprint!("Screenwindow is ignored in fullscreen mode"); + } else if sw.len() == 4 { + screen = Bounds2f::from_points( + Point2f::new(sw[0], sw[2]), + Point2f::new(sw[1], sw[3]), + ); } else { - if sw.len() == 4 { - screen = Bounds2f::from_points( - Point2f::new(sw[0], sw[2]), - Point2f::new(sw[1], sw[3]), - ); - } else { - return Err(anyhow!( - "{}: screenwindow param must have four values", - loc - )); - } + return Err(anyhow!( + "{}: screenwindow param must have four values", + loc + )); } } let camera = OrthographicCamera::new(base, screen, lens_radius, focal_distance); @@ -236,7 +232,7 @@ impl CameraFactory for Camera { PixelFormat::F32, Point2i::new(builtin_res, builtin_res), &["Y"], - SRGB.into(), + SRGB, ); let res = image.resolution(); @@ -287,7 +283,7 @@ impl CameraFactory for Camera { PixelFormat::F32, Point2i::new(builtin_res, builtin_res), &["Y"], - SRGB.into(), + SRGB, ); let res = img.resolution(); for y in 0..res.y() { @@ -309,7 +305,7 @@ impl CameraFactory for Camera { PixelFormat::F32, Point2i::new(builtin_res, builtin_res), &["Y"], - SRGB.into(), + SRGB, ); let low = (0.25 * builtin_res as Float) as i32; let high = (0.75 * builtin_res as Float) as i32; @@ -358,7 +354,7 @@ impl CameraFactory for Camera { PixelFormat::F32, im.image.resolution(), &["Y"], - SRGB.into(), + SRGB, ); let res = mono.resolution(); for y in 0..res.y() { diff --git a/src/core/color.rs b/src/core/color.rs index cf6e700..dccc9ee 100644 --- a/src/core/color.rs +++ b/src/core/color.rs @@ -1,8 +1,7 @@ use crate::utils::read_float_file; use anyhow::Result; use shared::core::color::{Coeffs, RES, RGBToSpectrumTable}; -use shared::{Float, Ptr, gvec_from_slice}; -use std::ops::Deref; +use shared::{Float, gvec_from_slice}; use std::path::Path; pub trait CreateRGBToSpectrumTable { diff --git a/src/core/film.rs b/src/core/film.rs index a81eb13..f9bfc1b 100644 --- a/src/core/film.rs +++ b/src/core/film.rs @@ -96,12 +96,12 @@ impl CreatePixelSensor for PixelSensor { }; if sensor_name == "cie1931" { - return Ok(Self::new_with_white_balance( + Ok(Self::new_with_white_balance( output_colorspace.as_ref(), sensor_illum.as_deref(), imaging_ratio, arena - )); + )) } else { let r_opt = get_named_spectrum(&format!("{}_r", sensor_name)); let g_opt = get_named_spectrum(&format!("{}_g", sensor_name)); @@ -118,7 +118,7 @@ impl CreatePixelSensor for PixelSensor { let g = g_opt.unwrap(); let b = b_opt.unwrap(); - return Ok(Self::new( + Ok(Self::new( &r, &g, &b, @@ -130,7 +130,7 @@ impl CreatePixelSensor for PixelSensor { ), imaging_ratio, arena - )); + )) } } @@ -176,7 +176,7 @@ impl CreatePixelSensor for PixelSensor { let sensor_white_g = illum.inner_product(&Spectrum::Dense(g_ptr)); let sensor_white_y = illum.inner_product(&Spectrum::Dense(spectra.y)); for i in 0..N_SWATCH_REFLECTANCES { - let s = swatches[i].clone(); + let s = swatches[i]; let xyz = PixelSensor::project_reflectance::( &s, illum, @@ -184,7 +184,7 @@ impl CreatePixelSensor for PixelSensor { &Spectrum::Dense(spectra.y), &Spectrum::Dense(spectra.z), ) * (sensor_white_y / sensor_white_g); - for c in 0..3 as u32 { + for c in 0..3_u32 { xyz_output[i][c as usize] = xyz[c].try_into().unwrap(); } } @@ -362,7 +362,7 @@ pub trait FilmTrait: Sync { }) .collect(); - let mut image = HostImage::new(format, resolution, channel_names, SRGB.into()); + let mut image = HostImage::new(format, resolution, channel_names, SRGB); let _rgb_desc = ImageChannelDesc::new(&[0, 1, 2]); for (iy, row_data) in processed_rows.into_iter().enumerate() { diff --git a/src/core/filter.rs b/src/core/filter.rs index beefd02..ac82274 100644 --- a/src/core/filter.rs +++ b/src/core/filter.rs @@ -2,10 +2,8 @@ use crate::Arena; use crate::{FileLoc, ParameterDictionary}; use anyhow::{bail, Result}; use shared::core::filter::Filter; -use shared::core::geometry::{Bounds2f, Point2f, Vector2f}; +use shared::core::geometry::Vector2f; use shared::filters::*; -use shared::utils::sampling::PiecewiseConstant2D; -use shared::{Array2D, Float}; pub trait FilterFactory { fn create( diff --git a/src/core/image/io.rs b/src/core/image/io.rs index 0845060..476b5b6 100644 --- a/src/core/image/io.rs +++ b/src/core/image/io.rs @@ -302,7 +302,7 @@ fn read_pfm(path: &Path) -> Result { let names: &[&str] = if channels == 1 { &["Y"] } else { &["R", "G", "B"] }; - let image = HostImage::new(PixelFormat::F32, Point2i::new(w, h), names, LINEAR.into()); + let image = HostImage::new(PixelFormat::F32, Point2i::new(w, h), names, LINEAR); let metadata = ImageMetadata::default(); Ok(ImageAndMetadata { image, metadata }) diff --git a/src/core/image/mod.rs b/src/core/image/mod.rs index 21626de..9081f1c 100644 --- a/src/core/image/mod.rs +++ b/src/core/image/mod.rs @@ -1,14 +1,12 @@ use anyhow::{anyhow, Result}; -use half::f16; use rayon::prelude::{IndexedParallelIterator, ParallelIterator, ParallelSliceMut}; -use shared::core::color::{ColorEncoding, ColorEncodingTrait, LINEAR}; +use shared::core::color::ColorEncoding; use shared::core::geometry::{Bounds2f, Point2f, Point2i}; -use shared::core::image::{Image, ImageBase, PixelFormat, Pixels, WrapMode, WrapMode2D}; +use shared::core::image::{Image, PixelFormat, WrapMode, WrapMode2D}; use shared::utils::math::square; -use shared::{Array2D, Float, Ptr}; -use smallvec::{smallvec, SmallVec}; +use shared::{Array2D, Float}; +use smallvec::SmallVec; use std::ops::{Deref, DerefMut}; -use std::sync::Arc; pub mod io; pub mod metadata; diff --git a/src/core/image/ops.rs b/src/core/image/ops.rs index 07c7a73..9c0c6d7 100644 --- a/src/core/image/ops.rs +++ b/src/core/image/ops.rs @@ -1,6 +1,5 @@ use super::HostImage; use rayon::prelude::*; -use shared::core::color::ColorEncoding; use shared::core::geometry::{Bounds2i, Point2i}; use shared::core::image::{PixelFormat, WrapMode, WrapMode2D}; use shared::utils::math::windowed_sinc; @@ -109,7 +108,7 @@ impl HostImage { PixelFormat::F32, new_res, &self.channel_names, - self.encoding().into(), + self.encoding(), ))); let x_weights = resample_weights(res.x() as usize, new_res.x() as usize); diff --git a/src/core/interaction.rs b/src/core/interaction.rs index 3e56bb2..0f8757e 100644 --- a/src/core/interaction.rs +++ b/src/core/interaction.rs @@ -37,7 +37,7 @@ impl InteractionGetter for SurfaceInteraction { camera: &Camera, sampler: &mut Sampler, ) -> Option { - self.compute_differentials(r, camera, sampler.samples_per_pixel() as i32); + self.compute_differentials(r, camera, sampler.samples_per_pixel()); let material = { let Some(mut active_mat) = self.material.get() else { return None; @@ -47,7 +47,7 @@ impl InteractionGetter for SurfaceInteraction { let ctx = MaterialEvalContext::from(&*self); active_mat = mix.choose_material(&tex_eval, &ctx)?; } - active_mat.clone() + *active_mat }; let ctx = MaterialEvalContext::from(&*self); let tex_eval = UniversalTextureEvaluator; @@ -82,7 +82,7 @@ impl InteractionGetter for SurfaceInteraction { let ctx = MaterialEvalContext::from(self); active_mat = mix.choose_material(&tex_eval, &ctx)?; } - let material = active_mat.clone(); + let material = *active_mat; let ctx = MaterialEvalContext::from(self); material.get_bssrdf(&tex_eval, &ctx, lambda) } diff --git a/src/core/light.rs b/src/core/light.rs index 6bb4499..a7a884c 100644 --- a/src/core/light.rs +++ b/src/core/light.rs @@ -14,7 +14,7 @@ use std::sync::Arc; pub fn lookup_spectrum(s: &Spectrum) -> Arc { let cache = &SPECTRUM_CACHE; let dense_spectrum = DenselySampledSpectrum::from_spectrum(s); - cache.lookup(dense_spectrum).into() + cache.lookup(dense_spectrum) } fn dummy_shape() -> Shape { @@ -60,7 +60,7 @@ pub fn create_light( &shape, &alpha, None, arena, ), "infinite" => crate::lights::infinite::create( - render_from_light, medium.into(), camera_transform, + render_from_light, medium, camera_transform, parameters, None, loc, arena, ), "diffuse" => Err(anyhow!( diff --git a/src/core/render.rs b/src/core/render.rs index 32ee6f0..a10651c 100644 --- a/src/core/render.rs +++ b/src/core/render.rs @@ -11,7 +11,6 @@ use shared::core::primitive::PrimitiveTrait; use shared::core::sampler::CameraSample; use shared::spectra::{SampledWavelengths, LAMBDA_MAX, LAMBDA_MIN}; use shared::Float; -use std::sync::Arc; pub fn render_scene(scene: &BasicScene, arena: &Arena) -> Result<()> { let media = scene.create_media(); @@ -19,7 +18,7 @@ pub fn render_scene(scene: &BasicScene, arena: &Arena) -> Result<()> { let (named_materials, materials) = scene.create_materials(&textures, arena)?; let lights = scene.create_lights(&textures, arena); - let have_scattering = { + let _have_scattering = { let shapes = scene.shapes.lock(); let animated = scene.animated_shapes.lock(); shapes @@ -37,16 +36,9 @@ pub fn render_scene(scene: &BasicScene, arena: &Arena) -> Result<()> { all_lights.extend(area_lights); let camera = scene.get_camera().unwrap(); - let film = camera.get_film(); + let _film = camera.get_film(); warn!("Creating integrator"); let sampler = scene.get_sampler()?; - let integrator = scene.create_integrator( - camera.clone(), - sampler.clone(), - aggregate.clone(), - all_lights, - arena, - ); if get_options().pixel_material.is_some() { let lambda = @@ -96,22 +88,42 @@ pub fn render_scene(scene: &BasicScene, arena: &Arena) -> Result<()> { depth += 1; ray = intr.spawn_ray(ray.d); } + } else if depth == 1 { + bail!("No geometry visible from pixel") } else { - if depth == 1 { - bail!("No geometry visible from pixel") - } else { - break; - } + break; } } } - render( - &integrator, - &integrator.base, - &camera, - sampler.as_ref(), - arena, - ); + if get_options().wavefront { + eprintln!("RENDER: Wavefront backend"); + let mut wf = scene.create_wavefront_integrator( + camera.clone(), + sampler.clone(), + aggregate.clone(), + all_lights, + arena, + ); + wf.render(); + } else { + eprintln!("RENDER: Path integrator backend"); + let integrator = scene.create_integrator( + camera.clone(), + sampler.clone(), + aggregate.clone(), + all_lights, + arena, + ); + render( + &integrator, + &integrator.base, + &camera, + sampler.as_ref(), + arena, + ); + } + Ok(()) + } diff --git a/src/core/scene/builder.rs b/src/core/scene/builder.rs index 3804c75..e4638ff 100644 --- a/src/core/scene/builder.rs +++ b/src/core/scene/builder.rs @@ -260,7 +260,7 @@ impl ParserTarget for BasicSceneBuilder { fn color_space(&mut self, name: &str, loc: FileLoc) -> Result<(), ParserError> { let stdcs = get_colorspace_device(); - let _ = match stdcs.get_named(name) { + match stdcs.get_named(name) { Some(cs) => { self.graphics_state.color_space = Some(Arc::new(*cs.get().unwrap())); } @@ -591,7 +591,7 @@ impl ParserTarget for BasicSceneBuilder { self.current_accelerator .take() .expect("Accelerator not set before WorldBegin"), - &arena, + arena, ); Ok(()) } diff --git a/src/core/scene/scene.rs b/src/core/scene/scene.rs index 1f8d72f..48595fe 100644 --- a/src/core/scene/scene.rs +++ b/src/core/scene/scene.rs @@ -11,6 +11,7 @@ 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::lights::sampler::create_light_sampler; use crate::utils::parallel::{run_async, AsyncJob}; use crate::utils::parameters::{NamedTextures, ParameterDictionary, TextureParameterDictionary}; use crate::utils::resolve_filename; @@ -19,21 +20,22 @@ use anyhow::{anyhow, Result}; use parking_lot::Mutex; use shared::core::aggregates::{BVHAggregate, SplitMethod}; use shared::core::camera::CameraTrait; -use shared::core::camera::{Camera, CameraTransform}; +use shared::core::camera::Camera; use shared::core::color::LINEAR; use shared::core::film::Film; use shared::core::filter::Filter; -use shared::core::light::Light; +use shared::core::light::{Light, LightTrait}; use shared::core::material::Material; use shared::core::medium::{Medium, MediumInterface}; use shared::core::primitive::{AnimatedPrimitive, GeometricPrimitive, Primitive, SimplePrimitive}; -use shared::core::sampler::Sampler; +use shared::core::sampler::{Sampler, SamplerTrait}; use shared::core::shape::Shape; -use shared::core::texture::{GPUFloatTexture, SpectrumType}; -use shared::lights::sampler::LightSampler; +use shared::core::texture::SpectrumType; use shared::spectra::RGBColorSpace; use shared::textures::FloatConstantTexture; -use shared::{Ptr, Transform}; +use shared::utils::soa::SoA; +use shared::wavefront::*; +use shared::{gvec, Ptr, WorkQueue}; use std::collections::HashMap; use std::sync::Arc; @@ -80,7 +82,7 @@ fn resolve_material( arena: &Arena, ) -> Material { match mat_ref { - MaterialRef::Name(name) => match named_materials.get(name) { + MaterialRef::Name(name) => match named_materials.get(name) { Some(m) => *m, None => { log::error!("{}: named material '{}' not found", loc, name); @@ -146,6 +148,12 @@ pub struct BasicScene { pub film_state: Mutex>, } +impl Default for BasicScene { + fn default() -> Self { + Self::new() + } +} + impl BasicScene { pub fn new() -> Self { Self { @@ -196,7 +204,7 @@ impl BasicScene { &film.parameters, exposure_time, filter, - Some(camera.camera_transform.clone()), + Some(camera.camera_transform), &film.loc, arena, ) @@ -508,12 +516,12 @@ impl BasicScene { state.map.clone() } - pub fn create_lights(&self, textures: &NamedTextures, arena: &Arena) -> Vec> { + pub fn create_lights(&self, _textures: &NamedTextures, arena: &Arena) -> Vec> { let light_state = self.light_state.lock(); let camera = self .get_camera() .expect("Camera must be initialized before lights"); - let camera_transform = camera.base().camera_transform.clone(); + let camera_transform = camera.base().camera_transform; let mut lights: Vec> = Vec::new(); // Non-area lights created from stored entities @@ -533,7 +541,7 @@ impl BasicScene { medium.map(|m| *m), &entity.transformed_base.base.parameters, &entity.transformed_base.base.loc, - camera_transform.clone(), + camera_transform, arena, ) { Ok(light) => lights.push(Arc::new(light)), @@ -709,6 +717,90 @@ impl BasicScene { } } + pub fn create_wavefront_integrator( + &self, + camera: Arc, + sampler: Arc, + aggregate: Arc, + lights: Vec>, + arena: &Arena, + ) -> WavefrontPathIntegrator { + let entity = self.integrator.lock().clone().unwrap(); + let max_depth = entity + .parameters + .get_one_int("maxdepth", 5) + .expect("Could not obtain depth value"); + let regularize = entity + .parameters + .get_one_bool("regularize", false) + .expect("Could not obtain regularize flag value"); + + let spp = sampler.samples_per_pixel() as u32; + let film = camera.base().film; + let pixel_bounds = film.pixel_bounds(); + let filter = Ptr::from(&film.base().filter); + let light_sampler = create_light_sampler("power", &lights, arena); + let res_x = pixel_bounds.diagonal().x() as u32; + let max_samples = 1024u32 * 1024; + let scanlines_per_pass = (max_samples / res_x).max(1); + let max_queue_size = res_x * scanlines_per_pass; + + let mut infinite_lights = gvec(); + for light in &lights { + if light.light_type().is_infinite() { + infinite_lights.push(arena.alloc(**light)); + } + } + + let cpu_aggregate = CpuAggregate::new(*aggregate); + + WavefrontPathIntegrator { + aggregate: cpu_aggregate, + camera: (*camera).clone(), + sampler: (*sampler).clone(), + max_depth: max_depth.try_into().unwrap(), + film, + filter, + samples_per_pixel: spp, + regularize, + infinite_lights, + max_queue_size, + scanlines_per_pass, + light_sampler, + ray_queues: [ + WorkQueue::new( + RayWorkItemSoA::allocate(max_queue_size, arena), + max_queue_size, + ), + WorkQueue::new( + RayWorkItemSoA::allocate(max_queue_size, arena), + max_queue_size, + ), + ], + shadow_ray_queue: WorkQueue::new( + ShadowRayWorkItemSoA::allocate(max_queue_size, arena), + max_queue_size, + ), + escaped_ray_queue: WorkQueue::new( + EscapedRayWorkItemSoA::allocate(max_queue_size, arena), + max_queue_size, + ), + hit_area_light_queue: WorkQueue::new( + HitAreaLightWorkItemSoA::allocate(max_queue_size, arena), + max_queue_size, + ), + basic_eval_material_queue: WorkQueue::new( + MaterialEvalWorkItemSoA::allocate(max_queue_size, arena), + max_queue_size, + ), + universal_eval_material_queue: WorkQueue::new( + MaterialEvalWorkItemSoA::allocate(max_queue_size, arena), + max_queue_size, + ), + pixel_sample_state: PixelSampleState::allocate(max_queue_size, arena), + } + } + // Getters pub fn get_camera(&self) -> Result> { @@ -847,7 +939,7 @@ impl BasicScene { shape, arena.alloc(mtl), light_ptr, - mi.clone(), + mi, alpha_ptr, )) }; @@ -866,9 +958,9 @@ impl BasicScene { materials: &[Material], light_state: &LightState, media: &HashMap>, - film_cs: Option<&RGBColorSpace>, + _film_cs: Option<&RGBColorSpace>, arena: &Arena, - area_lights: &mut Vec>, + _area_lights: &mut Vec>, ) -> Vec { let mut primitives = Vec::new(); @@ -945,7 +1037,7 @@ impl BasicScene { shape, arena.alloc(mtl), Ptr::null(), // no area light on animated shapes - mi.clone(), + mi, alpha_ptr, )) }; @@ -1103,7 +1195,6 @@ impl BasicScene { Err(anyhow!("{} requested but not initialized!", name)) } - #[allow(dead_code)] fn upload_shapes( &self, diff --git a/src/films/gbuffer.rs b/src/films/gbuffer.rs index 73b8a82..fa95e15 100644 --- a/src/films/gbuffer.rs +++ b/src/films/gbuffer.rs @@ -3,7 +3,6 @@ use crate::core::film::{CreateFilmBase, CreatePixelSensor}; use shared::core::film::PixelSensor; use anyhow::{Result, anyhow}; use shared::core::film::{FilmBase, GBufferFilm}; -use shared::spectra::RGBColorSpace; use shared::utils::AnimatedTransform; use std::path::Path; @@ -26,7 +25,7 @@ impl CreateFilm for GBufferFilm { let filename = params.get_one_string("filename", "pbrt.exr")?; if Path::new(&filename).extension() != Some("exr".as_ref()) { - return Err(anyhow!("{}: EXR is the only format supported by GBufferFilm", loc).into()); + return Err(anyhow!("{}: EXR is the only format supported by GBufferFilm", loc)); } let coords_system = params.get_one_string("coordinatesystem", "camera")?; @@ -43,8 +42,7 @@ impl CreateFilm for GBufferFilm { "{}: unknown coordinate system for GBufferFilm. (Expecting camera or world", loc - ) - .into()); + )); }; let film = GBufferFilm::new( diff --git a/src/films/mod.rs b/src/films/mod.rs index bcc788e..5ba9997 100644 --- a/src/films/mod.rs +++ b/src/films/mod.rs @@ -9,9 +9,6 @@ pub mod gbuffer; pub mod rgb; pub mod spectral; -pub use gbuffer::*; -pub use rgb::*; -pub use spectral::*; pub trait CreateFilm { fn create( diff --git a/src/films/rgb.rs b/src/films/rgb.rs index 78f5a51..755e760 100644 --- a/src/films/rgb.rs +++ b/src/films/rgb.rs @@ -3,8 +3,7 @@ use crate::core::film::{CreateFilmBase, CreatePixelSensor}; use crate::Arena; use anyhow::Result; use shared::core::camera::CameraTransform; -use shared::core::film::{Film, FilmBase, RGBFilm, RGBPixel, PixelSensor}; -use shared::spectra::RGBColorSpace; +use shared::core::film::{Film, FilmBase, RGBFilm, PixelSensor}; impl CreateFilm for RGBFilm { fn create( diff --git a/src/films/spectral.rs b/src/films/spectral.rs index 0e64792..6787c7d 100644 --- a/src/films/spectral.rs +++ b/src/films/spectral.rs @@ -5,7 +5,6 @@ use anyhow::{anyhow, Result}; use shared::core::camera::CameraTransform; use shared::core::film::{FilmBase, PixelSensor, SpectralFilm}; use shared::spectra::{LAMBDA_MAX, LAMBDA_MIN}; -use shared::utils::math::SquareMatrix; use shared::Float; use std::path::Path; @@ -28,7 +27,7 @@ impl CreateFilm for SpectralFilm { let filename = params.get_one_string("filename", "pbrt.exr")?; if Path::new(&filename).extension() != Some("exr".as_ref()) { - return Err(anyhow!("{}: EXR is the only format supported by GBufferFilm", loc).into()); + return Err(anyhow!("{}: EXR is the only format supported by GBufferFilm", loc)); } let n_buckets = params.get_one_int("nbuckets", 16)? as usize; diff --git a/src/integrators/mod.rs b/src/integrators/mod.rs index 2bb690e..9f08671 100644 --- a/src/integrators/mod.rs +++ b/src/integrators/mod.rs @@ -15,7 +15,6 @@ 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; @@ -58,7 +57,7 @@ impl CreateIntegrator for PathIntegrator { fn create( parameters: ParameterDictionary, camera: Arc, - sampler: Arc, + _sampler: Arc, aggregate: Arc, lights: Vec>, config: PathConfig, diff --git a/src/integrators/path.rs b/src/integrators/path.rs index 3a13f85..a354cc4 100644 --- a/src/integrators/path.rs +++ b/src/integrators/path.rs @@ -126,7 +126,7 @@ impl PathIntegrator { if !self .base - .unoccluded(&Interaction::Surface(intr.clone()), &ls.p_light) + .unoccluded(&Interaction::Surface(*intr), &ls.p_light) { return SampledSpectrum::zero(); } @@ -232,15 +232,14 @@ impl RayIntegratorTrait for PathIntegrator { if !le.is_black() { if state.depth == 0 || state.specular_bounce { state.l += state.beta * le; - } else if self.config.use_mis { - if !isect.area_light.is_null() { + } else if self.config.use_mis + && !isect.area_light.is_null() { let light = &isect.area_light; let p_l = self.sampler.pmf_with_context(&state.prev_ctx, light) * light.pdf_li(&state.prev_ctx, ray.d, true); let w_b = power_heuristic(1, state.prev_pdf, 1, p_l); state.l += state.beta * w_b * le; } - } } // Get BSDF diff --git a/src/integrators/pipeline.rs b/src/integrators/pipeline.rs index 6949ec8..3a87cfd 100644 --- a/src/integrators/pipeline.rs +++ b/src/integrators/pipeline.rs @@ -9,14 +9,13 @@ use crate::Arena; use indicatif::{ProgressBar, ProgressStyle}; use rayon::iter::{IntoParallelRefIterator, ParallelIterator}; use shared::core::camera::{Camera, CameraTrait}; -use shared::core::geometry::{Bounds2i, Point2i, VectorLike}; +use shared::core::geometry::{Bounds2i, Point2i}; use shared::core::sampler::get_camera_sample; use shared::core::sampler::{Sampler, SamplerTrait}; use shared::spectra::SampledSpectrum; use shared::Float; use std::io::Write; use std::path::Path; -use std::sync::Arc; struct PbrtProgress { bar: ProgressBar, @@ -95,7 +94,7 @@ pub fn render( &mut tile_sampler, p_pixel, s_index, - &arena, + arena, ); return; } @@ -158,7 +157,7 @@ pub fn render( if let Some(out_path) = &options.mse_reference_output { mse_out_file = Some( std::fs::File::create(out_path) - .expect(&format!("Failed to create MSE output file: {}", out_path)), + .unwrap_or_else(|_| panic!("Failed to create MSE output file: {}", out_path)), ); } } @@ -177,7 +176,7 @@ pub fn render( &mut sampler, p_pixel, sample_index.try_into().unwrap(), - &arena, + arena, ); } } @@ -200,7 +199,7 @@ pub fn render( if wave_start == spp || options.write_partial_images || reference_image.is_some() { let mut metadata = ImageMetadata { render_time_seconds: Some(progress.elapsed_seconds()), - samples_per_pixel: Some(wave_start as i32), + samples_per_pixel: Some(wave_start), ..Default::default() }; diff --git a/src/integrators/state.rs b/src/integrators/state.rs index e76d157..8e9213b 100644 --- a/src/integrators/state.rs +++ b/src/integrators/state.rs @@ -14,6 +14,12 @@ pub struct PathState { pub prev_pdf: Float, } +impl Default for PathState { + fn default() -> Self { + Self::new() + } +} + impl PathState { pub fn new() -> Self { Self { diff --git a/src/lib.rs b/src/lib.rs index 697e2ee..9e30345 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -15,3 +15,5 @@ pub mod wavefront; pub use utils::{Arena, FileLoc, ParameterDictionary, Upload, ArenaUpload}; pub const MAX_TAGS: u32 = 16; +pub use shared::{BasicPBRTOptions, PBRTOptions}; +pub use globals::{get_options, init_pbrt}; diff --git a/src/lights/distant.rs b/src/lights/distant.rs index 53b0b85..015b338 100644 --- a/src/lights/distant.rs +++ b/src/lights/distant.rs @@ -11,7 +11,7 @@ use shared::core::spectrum::Spectrum; use shared::core::texture::SpectrumType; use shared::lights::DistantLight; use shared::spectra::RGBColorSpace; -use shared::utils::{Ptr, Transform}; +use shared::utils::Transform; use shared::Float; trait CreateDistantLight { diff --git a/src/lights/goniometric.rs b/src/lights/goniometric.rs index f15aadf..7ce5e4e 100644 --- a/src/lights/goniometric.rs +++ b/src/lights/goniometric.rs @@ -69,12 +69,11 @@ pub fn create( scale /= spectrum_to_photometric(i); let phi_v = params.get_one_float("power", -1.0)?; - if phi_v > 0.0 { - if let Some(ref img) = host_image { + if phi_v > 0.0 + && let Some(ref img) = host_image { let k_e = compute_emissive_power(img); scale *= phi_v / k_e; } - } let swap_yz: [Float; 16] = [ 1., 0., 0., 0., 0., 0., 1., 0., 0., 1., 0., 0., 0., 0., 0., 1., @@ -146,7 +145,7 @@ fn convert_to_luminance_image( } } - Ok(HostImage::from_f32(&y_pixels, res, &["Y"].to_vec())) + Ok(HostImage::from_f32(&y_pixels, res, ["Y"].as_ref())) } (Err(_), Ok(_)) => { diff --git a/src/lights/infinite.rs b/src/lights/infinite.rs index 167986a..ea4a08a 100644 --- a/src/lights/infinite.rs +++ b/src/lights/infinite.rs @@ -9,7 +9,7 @@ use rayon::prelude::*; use shared::core::camera::CameraTransform; use shared::core::geometry::{cos_theta, Bounds2f, Frame, Point2f, Point2i, Point3f, VectorLike}; use shared::core::image::WrapMode; -use shared::core::light::{Light, LightBase, LightType}; +use shared::core::light::Light; use shared::core::medium::Medium; use shared::core::spectrum::Spectrum; use shared::core::texture::SpectrumType; @@ -17,7 +17,7 @@ use shared::lights::{ImageInfiniteLight, PortalInfiniteLight, UniformInfiniteLig use shared::spectra::RGBColorSpace; use shared::utils::math::{equal_area_sphere_to_square, equal_area_square_to_sphere}; use shared::utils::sampling::{PiecewiseConstant2D, WindowedPiecewiseConstant2D}; -use shared::{Float, Ptr, Transform, PI}; +use shared::{Float, Transform, PI}; use std::path::Path; pub fn create( @@ -275,7 +275,7 @@ fn load_image( let rgb = l[0].to_rgb(colorspace, &stdspec); let image = HostImage::new_constant(Point2i::new(1, 1), &["R", "G", "B"], &[rgb.r, rgb.g, rgb.b]); - return Ok((image, colorspace.clone())); + return Ok((image, *colorspace)); } let im = HostImage::read(Path::new(filename), None) @@ -290,7 +290,7 @@ fn load_image( .get_channel_desc(&["R", "G", "B"]) .map_err(|_| anyhow!("image '{}' must have R, G, B channels", filename))?; - let cs = im.metadata.colorspace.unwrap_or_else(|| colorspace.clone()); + let cs = im.metadata.colorspace.unwrap_or_else(|| *colorspace); Ok((im.image.select_channels(&desc), cs)) } diff --git a/src/lights/point.rs b/src/lights/point.rs index ed7833e..9cc27f1 100644 --- a/src/lights/point.rs +++ b/src/lights/point.rs @@ -11,7 +11,7 @@ use shared::core::spectrum::Spectrum; use shared::core::texture::SpectrumType; use shared::lights::PointLight; use shared::spectra::RGBColorSpace; -use shared::{Float, PI, Ptr, Transform}; +use shared::{Float, PI, Transform}; pub trait CreatePointLight { fn new( diff --git a/src/lights/sampler.rs b/src/lights/sampler.rs index 149e1e4..617d411 100644 --- a/src/lights/sampler.rs +++ b/src/lights/sampler.rs @@ -1,7 +1,7 @@ use crate::Arena; use shared::core::light::{Light, LightTrait}; use shared::lights::sampler::{ - BVHLightSampler, LightSampler, PowerLightSampler, UniformLightSampler, + LightSampler, PowerLightSampler, UniformLightSampler, }; use shared::utils::sampling::AliasTable; use shared::spectra::{SampledSpectrum, SampledWavelengths}; diff --git a/src/lights/spot.rs b/src/lights/spot.rs index d13942b..7311bbf 100644 --- a/src/lights/spot.rs +++ b/src/lights/spot.rs @@ -12,7 +12,7 @@ use shared::core::texture::SpectrumType; use shared::lights::SpotLight; use shared::spectra::RGBColorSpace; use shared::utils::math::radians; -use shared::{Float, Ptr, Transform, PI}; +use shared::{Float, Transform, PI}; trait CreateSpotLight { fn new( diff --git a/src/samplers/halton.rs b/src/samplers/halton.rs index a1639b3..fae8f22 100644 --- a/src/samplers/halton.rs +++ b/src/samplers/halton.rs @@ -23,7 +23,7 @@ impl CreateHaltonSampler for HaltonSampler { full_res: Point2i, randomize: RandomizeStrategy, seed: u64, - arena: &Arena, + _arena: &Arena, ) -> Self { let digit_permutations = compute_radical_inverse_permutations(seed); let leaked = Box::leak(gbox(digit_permutations)); diff --git a/src/shapes/bilinear.rs b/src/shapes/bilinear.rs index 88f65c6..a8f65f0 100644 --- a/src/shapes/bilinear.rs +++ b/src/shapes/bilinear.rs @@ -63,7 +63,7 @@ impl CreateShape for BilinearPatchShape { n.clear(); } - for (_, &idx) in vertex_indices.iter().enumerate() { + for &idx in vertex_indices.iter() { if idx < 0 || idx as usize >= p.len() { return Err(anyhow!( "Bilinear patch mesh has out-of-bounds vertex index {} ({} \"P\" values were given). Discarding this mesh.", diff --git a/src/shapes/curves.rs b/src/shapes/curves.rs index 61ed919..cefb697 100644 --- a/src/shapes/curves.rs +++ b/src/shapes/curves.rs @@ -45,7 +45,7 @@ pub fn create_curve( let u_max = (i + 1) as Float / n_segments as Float; let curve = CurveShape { - common: curve_common.clone(), + common: curve_common, u_min, u_max, }; @@ -90,7 +90,7 @@ impl CreateShape for CurveShape { if basis == "bezier" { if cp.len() <= degree as usize - || ((cp.len() - 1 - degree as usize) % degree as usize) != 0 + || !(cp.len() - 1 - degree as usize).is_multiple_of(degree as usize) { return Err(anyhow!( "Invalid number of control points {}: for the degree {} Bezier basis {} + n * {} are required.", diff --git a/src/shapes/mesh.rs b/src/shapes/mesh.rs index b1c2166..18a23df 100644 --- a/src/shapes/mesh.rs +++ b/src/shapes/mesh.rs @@ -3,7 +3,6 @@ use ply_rs::parser::Parser; use ply_rs::ply::{DefaultElement, Property}; use shared::core::geometry::{Normal3f, Point2f, Point3f, VectorLike}; use shared::shapes::mesh::TriangleMesh; -use shared::utils::sampling::PiecewiseConstant2D; use shared::Transform; use std::fs::File; use std::path::Path; @@ -214,9 +213,9 @@ impl TriQuadMesh { let v20 = p2 - p0; let face_normal = v10.cross(v20); - self.n[i0] = self.n[i0] + Normal3f::from(face_normal); - self.n[i1] = self.n[i1] + Normal3f::from(face_normal); - self.n[i2] = self.n[i2] + Normal3f::from(face_normal); + self.n[i0] += Normal3f::from(face_normal); + self.n[i1] += Normal3f::from(face_normal); + self.n[i2] += Normal3f::from(face_normal); } for quad in self.quad_indices.chunks_exact(4) { @@ -231,14 +230,14 @@ impl TriQuadMesh { let face_normal = v10.cross(v20); for &idx in &indices { - self.n[idx] = self.n[idx] + Normal3f::from(face_normal); + self.n[idx] += Normal3f::from(face_normal); } } for normal in &mut self.n { let len_sq = normal.norm_squared(); if len_sq > 0.0 { - *normal = *normal / len_sq.sqrt(); + *normal /= len_sq.sqrt(); } } } diff --git a/src/shapes/triangle.rs b/src/shapes/triangle.rs index 3071ff1..ecb79fb 100644 --- a/src/shapes/triangle.rs +++ b/src/shapes/triangle.rs @@ -60,7 +60,7 @@ impl CreateShape for TriangleShape { n.clear(); } - for (_, &index) in vertex_indices.iter().enumerate() { + for &index in vertex_indices.iter() { // Check for negative indices (if keeping i32) or out of bounds if index < 0 || index as usize >= p.len() { bail!( diff --git a/src/spectra/colorspace.rs b/src/spectra/colorspace.rs index d6006f2..3a527c8 100644 --- a/src/spectra/colorspace.rs +++ b/src/spectra/colorspace.rs @@ -5,7 +5,6 @@ use shared::core::spectrum::Spectrum; use shared::spectra::{DenselySampledSpectrum, RGBColorSpace}; use shared::utils::math::SquareMatrix; use shared::Ptr; -use std::sync::Arc; pub trait CreateRGBColorSpace { fn new( diff --git a/src/spectra/data.rs b/src/spectra/data.rs index 6ac8454..9f16eff 100644 --- a/src/spectra/data.rs +++ b/src/spectra/data.rs @@ -1,7 +1,7 @@ use shared::core::spectrum::Spectrum; use shared::spectra::cie::*; use shared::spectra::{DenselySampledSpectrum, PiecewiseLinearSpectrum}; -use shared::{gbox, gvec_from_slice, leak, Float, Ptr}; +use shared::{gvec_from_slice, leak, Float}; use std::collections::HashMap; use std::sync::LazyLock; diff --git a/src/spectra/mod.rs b/src/spectra/mod.rs index dd18f4e..00183d3 100644 --- a/src/spectra/mod.rs +++ b/src/spectra/mod.rs @@ -107,10 +107,10 @@ impl StandardColorSpaces { pub fn get_colorspace_context() -> StandardColorSpaces { StandardColorSpaces { - srgb: SRGB.clone().into(), - dci_p3: DCI_P3.clone().into(), - rec2020: REC2020.clone().into(), - aces2065_1: ACES.clone().into(), + srgb: SRGB.clone(), + dci_p3: DCI_P3.clone(), + rec2020: REC2020.clone(), + aces2065_1: ACES.clone(), } } diff --git a/src/utils/containers.rs b/src/utils/containers.rs index 763f50d..3871485 100644 --- a/src/utils/containers.rs +++ b/src/utils/containers.rs @@ -1,5 +1,4 @@ use parking_lot::Mutex; -use shared::core::geometry::{Bounds2i, Point2i}; use std::collections::HashSet; use std::hash::Hash; use std::sync::Arc; @@ -8,6 +7,15 @@ pub struct InternCache { cache: Mutex>>, } +impl Default for InternCache +where + T: Eq + Clone + Hash, + { + fn default() -> Self { + Self::new() + } +} + impl InternCache where T: Eq + Clone + Hash, diff --git a/src/utils/mipmap.rs b/src/utils/mipmap.rs index 82efb1d..1040c37 100644 --- a/src/utils/mipmap.rs +++ b/src/utils/mipmap.rs @@ -157,7 +157,7 @@ impl MIPMap { } pub fn get_rgb_colorspace(&self) -> Option { - self.color_space.clone() + self.color_space } pub fn get_level(&self, level: usize) -> &HostImage { diff --git a/src/utils/parallel.rs b/src/utils/parallel.rs index 77e166a..bebd1f7 100644 --- a/src/utils/parallel.rs +++ b/src/utils/parallel.rs @@ -1,5 +1,4 @@ use crossbeam_channel::{Receiver, bounded}; -use rayon::prelude::*; #[derive(Debug)] pub struct AsyncJob { diff --git a/src/utils/parameters.rs b/src/utils/parameters.rs index 40e9ec1..4df4332 100644 --- a/src/utils/parameters.rs +++ b/src/utils/parameters.rs @@ -515,7 +515,7 @@ impl ParameterDictionary { || param.type_name == "rgb" || param.type_name == "blackbody" { - return Some(self.extract_spectrum_array(param, stype)[0].clone()); + return Some(self.extract_spectrum_array(param, stype)[0]); } } } @@ -654,7 +654,7 @@ impl ParameterDictionary { } fn extract_sampled_spectrum(&self, param: &ParsedParameter) -> Vec { - if param.floats.len() % 2 != 0 { + if !param.floats.len().is_multiple_of(2) { panic!( "{}: Found odd number of values for '{}'", param.loc, param.name @@ -852,11 +852,11 @@ impl TextureParameterDictionary { pub fn get_float_texture(&self, name: &str, val: Float) -> Result> { if let Some(tex) = self.get_float_texture_or_null(name)? { - return Ok(tex); + Ok(tex) } else { - return Ok(Arc::new(FloatTexture::Constant(FloatConstantTexture::new( + Ok(Arc::new(FloatTexture::Constant(FloatConstantTexture::new( val, - )))); + )))) } } @@ -961,7 +961,7 @@ impl TextureParameterDictionary { _ => {} } } - return None; + None } pub fn get_float_texture_or_null(&self, name: &str) -> Result>> { diff --git a/src/utils/upload.rs b/src/utils/upload.rs index 3cb9490..a40d92c 100644 --- a/src/utils/upload.rs +++ b/src/utils/upload.rs @@ -102,7 +102,6 @@ fn convert_spectrum(tex: &SpectrumTexture, arena: &Arena) -> GPUSpectrumTexture t.base .mipmap .color_space - .clone() .unwrap_or_else(crate::spectra::default_colorspace), ), }) @@ -127,14 +126,14 @@ impl Upload for Arc { impl Upload for &FloatTexture { type Target = Ptr; fn upload(self, arena: &Arena) -> Self::Target { - arena.alloc(convert_float(&self, arena)) + arena.alloc(convert_float(self, arena)) } } impl Upload for &SpectrumTexture { type Target = Ptr; fn upload(self, arena: &Arena) -> Self::Target { - arena.alloc(convert_spectrum(&self, arena)) + arena.alloc(convert_spectrum(self, arena)) } } impl Upload for Option> { diff --git a/src/wavefront/aggregate.rs b/src/wavefront/aggregate.rs new file mode 100644 index 0000000..17200bd --- /dev/null +++ b/src/wavefront/aggregate.rs @@ -0,0 +1,181 @@ +use crate::core::texture::{BasicTextureEvaluator, TextureEvaluator, UniversalTextureEvaluator}; +use crate::core::primitive::{Primitive, PrimitiveTrait}; +use crate::core::geometry::{Bounds3f, Ray, Vector3f, VectorLike}; +use crate::core::interaction::InteractionTrait; +use crate::core::material::MaterialTrait; +use crate::globals::get_options; +use rayon::prelude::*; +use shared::wavefront::WavefrontAggregate; + +pub struct CpuAggregate { + pub aggregate: Primitive, +} + +impl CpuAggregate { + pub fn new(aggregate: Primitive) -> Self { + Self { aggregate } + } +} + +impl WavefrontAggregate for CpuAggregate { + fn bounds(&self) -> Bounds3f { + self.aggregate.bounds() + } + + fn intersect_closest( + &self, + max_rays: usize, + ray_q: &RayQueue, + escaped_ray_q: &EscapedRayQueue, + hit_area_light_q: &HitAreaLightQueue, + basic_eval_mtl_q: &MaterialEvalQueue, + universal_eval_mtl_q: &MaterialEvalQueue, + next_ray_q: &RayQueue, + pixel_sample_state: &PixelSampleState, + ) { + let n_rays = ray_q.size().min(max_rays as u32); + let options = get_options(); + + (0..n_rays as usize).into_par_iter().for_each(|i| { + let work = unsafe { ray_q.get(i) }; + + let ray = Ray::new(work.ray_o, work.ray_d, Some(work.ray_time), work.ray_medium); + + let pi = work.pixel_index as usize; + let beta = pixel_sample_state.beta.get(pi); + let r_u = pixel_sample_state.r_u.get(pi); + let r_l = pixel_sample_state.r_l.get(pi); + let lambda = pixel_sample_state.lambda.get(pi); + let depth = pixel_sample_state.depth.get(pi); + let specular_bounce = pixel_sample_state.specular_bounce.get(pi) != 0; + let any_non_specular = pixel_sample_state.any_non_specular_bounces.get(pi) != 0; + let eta_scale = pixel_sample_state.eta_scale.get(pi); + let prev_intr_ctx = pixel_sample_state.prev_intr_ctx.get(pi); + + let Some(si) = self.aggregate.intersect(&ray, None) else { + escaped_ray_q.push(EscapedRayWorkItem { + ray_o: work.ray_o, + ray_d: work.ray_d, + lambda, + pixel_index: work.pixel_index, + beta, + r_u, + r_l, + depth, + specular_bounce, + prev_intr_ctx, + }); + return; + }; + + let intr = &si.intr; + + if intr.material.is_null() { + let ray_o = Ray::offset_origin(&intr.pi(), &intr.n(), &work.ray_d); + let ray_medium = if work.ray_d.dot(intr.n().into()) > 0.0 { + intr.common.medium_interface.outside + } else { + intr.common.medium_interface.inside + }; + next_ray_q.push(RayWorkItem { + ray_o, + ray_d: work.ray_d, + ray_time: work.ray_time, + ray_medium, + has_differentials: work.has_differentials, + differential: work.differential, + pixel_index: work.pixel_index, + }); + return; + } + + // Check for area light hit + if !intr.area_light.is_null() { + hit_area_light_q.push(HitAreaLightWorkItem { + area_light: intr.area_light, + p: intr.p(), + n: intr.n(), + uv: intr.common.uv, + wo: -work.ray_d, + lambda, + pixel_index: work.pixel_index, + beta, + r_u, + r_l, + depth, + specular_bounce, + prev_intr_ctx, + }); + } + + // Determine which material evaluation queue to use based on + // whether the material's textures can be evaluated with the + // basic evaluator (cheaper) or need the universal one. + let material = *intr.material.get().unwrap(); + let eval_q = if material.can_evaluate_textures(&BasicTextureEvaluator) { + basic_eval_mtl_q + } else { + universal_eval_mtl_q + }; + + eval_q.push(MaterialEvalWorkItem { + p: intr.p(), + n: intr.n(), + ns: intr.shading.n, + dpdu: intr.shading.dpdu, + dpdv: intr.shading.dpdv, + uv: intr.common.uv, + wo: -work.ray_d, + time: work.ray_time, + face_index: intr.face_index, + material: intr.material, + area_light: intr.area_light, + medium_interface: intr.common.medium_interface, + pixel_index: work.pixel_index, + lambda, + beta, + r_u, + any_non_specular_bounces: any_non_specular, + depth, + eta_scale, + }); + }); + } + + fn intersect_shadow( + &self, + max_rays: usize, + shadow_ray_q: &ShadowRayQueue, + pixel_sample_state: &PixelSampleState, + ) { + let n_rays = shadow_ray_q.size().min(max_rays as u32); + + for i in 0..n_rays as usize { + let work = unsafe { shadow_ray_q.get(i) }; + + let ray = Ray::new( + work.ray_o, + work.ray_d, + Some(work.ray_time), + crate::Ptr::null(), + ); + + if !self.aggregate.intersect_p(&ray, Some(work.t_max)) { + let pi = work.pixel_index as usize; + let mut l = pixel_sample_state.l.get(pi); + l += work.l_d; + pixel_sample_state.l.set(pi, l); + } + } + } + + fn intersect_shadow_tr( + &self, + max_rays: usize, + shadow_ray_q: &ShadowRayQueue, + pixel_sample_state: &PixelSampleState, + ) { + self.intersect_shadow(max_rays, shadow_ray_q, pixel_sample_state); + } +} + diff --git a/src/wavefront/integrator.rs b/src/wavefront/integrator.rs index 9adf61c..e31eb61 100644 --- a/src/wavefront/integrator.rs +++ b/src/wavefront/integrator.rs @@ -1,38 +1,459 @@ -// use crate::MAX_TAGS; -// use shared::{Ptr, GVec}; -// use shared::core::film::Film; -// use shared::core::color::RGB; -// use shared::core::filter::Filter; -// use shared::core::light::Light; -// use shared::core::sampler::Sampler; -// use shared::wavefront::{WavefrontAggregate, RayQueue, MediumSampleQueue, EscapedRayQueue, HitAreaLightQueue, MaterialEvalQueue, ShadowRayQueue, GetBSSRDFAndProbeRayQueue, SubsurfaceScatterQueue}; -// -// pub struct WavefrontPathIntegrator { -// pub init_visible_surface: bool, -// pub have_subsurface: bool, -// pub have_media: bool, -// pub have_basic_eval_material: [bool; MAX_TAGS + 1], -// pub have_universal_eval_material: [bool; MAX_TAGS + 1], -// pub filter: Filter, -// pub film: Film, -// pub sampler: Sampler, -// pub camera: Camera, -// pub infinite_lights: GVec, -// pub max_depth: usize, -// pub sampler_per_pixel: usize, -// pub regularize: bool, -// pub scanlines_per_pixel: usize, -// pub max_queue_size: usize, -// pub medium_sample_queue: Ptr, -// pub medium_scatter_queue: Ptr, -// pub escaped_ray_queue: Ptr, -// pub hit_area_light_queue: Ptr, -// pub basic_eval_material_queue: Ptr, -// pub universal_eval_material_queue: Ptr, -// pub shadow_ray_queue: Ptr, -// pub bssrdf_eval_queue: PTr, -// pub subsurface_scatter_queue: Ptr, -// pub display_rgb: Ptr, -// pub display_rgb_host: Ptr, -// -// } +use super::CpuAggregate; +use crate::globals::get_options; +use shared::core::bxdf::{FArgs, TransportMode}; +use shared::core::camera::{Camera, CameraTrait}; +use shared::core::filter::{Filter, FilterTrait}; +use shared::core::geometry::{ + Bounds2i, Point2f, Point2i, Point3f, Point3fi, Ray, RayDifferential, Vector2f, Vector3f, + VectorLike, +}; +use shared::core::interaction::InteractionTrait; +use shared::core::light::{Light, LightSampleContext, LightTrait}; +use shared::core::material::{MaterialEvalContext, MaterialTrait}; +use shared::core::sampler::{CameraSample, Sampler, SamplerTrait}; +use shared::core::texture::{TextureEvalContext, UniversalTextureEvaluator}; +use shared::lights::sampler::{LightSampler, LightSamplerTrait}; +use shared::spectra::{SampledSpectrum, SampledWavelengths}; +use shared::utils::math::square; +use shared::utils::sampling::power_heuristic; +use shared::utils::soa::{SoA, SoAAllocator, WorkQueue}; +use shared::wavefront::{WavefrontAggregate, WavefrontPathIntegrator, WavefrontRenderer}; + +impl WavefrontRenderer for WavefrontPathIntegrator { + pub fn render(&mut self) { + let film = self.camera.get_film(); + let filter = film.get_filter(); + let pixel_bounds = film.pixel_bounds(); + let resolution = pixel_bounds.diagonal(); + + let total_work = (pixel_bounds.area() as u64) * (self.samples_per_pixel as u64); + let options = get_options(); + let progress = PbrtProgress::new(total_work, "Rendering", options.quiet); + + for sample_index in 0..self.samples_per_pixel { + // Process image in scanline batches + let mut y0 = pixel_bounds.p_min.y(); + while y0 < pixel_bounds.p_max.y() { + let y1 = (y0 + self.scanlines_per_pass as i32).min(pixel_bounds.p_max.y()); + + // Reset the primary ray queue for this set + self.ray_queues[0].reset(); + + self.generate_camera_rays(y0, y1, sample_index, &pixel_bounds); + + for depth in 0..=self.max_depth { + let current = (depth % 2) as usize; + let next = ((depth + 1) % 2) as usize; + + // Reset output queues before intersection + self.ray_queues[next].reset(); + self.escaped_ray_queue.reset(); + self.hit_area_light_queue.reset(); + self.basic_eval_material_queue.reset(); + self.universal_eval_material_queue.reset(); + self.shadow_ray_queue.reset(); + + // Skip if no rays to trace + if self.ray_queues[current].size() == 0 { + break; + } + + // Sorting of rays into output queues + self.aggregate.intersect_closest( + self.max_queue_size as usize, + &self.ray_queues[current], + &self.escaped_ray_queue, + &self.hit_area_light_queue, + &self.basic_eval_material_queue, + &self.universal_eval_material_queue, + &self.ray_queues[next], + &self.pixel_sample_state, + ); + + // Infinite light contributions + self.handle_escaped_rays(); + + // Area light contributions + self.handle_emissive_intersections(); + + // Last depth — don't evaluate materials or sample lights + if depth == self.max_depth { + break; + } + + // Evaluate materials, sample BSDFs, sample direct lighting + // This pushes to shadow_ray_queue and ray_queues[next] + self.evaluate_materials_and_bsdfs(depth); + + // Add direct lighting to pixels + self.aggregate.intersect_shadow( + self.max_queue_size as usize, + &self.shadow_ray_queue, + &self.pixel_sample_state, + ); + } + + self.update_film(y0, y1, &pixel_bounds); + let batch_pixels = + ((y1 - y0) * (pixel_bounds.p_max.x() - pixel_bounds.p_min.x())) as u64; + progress.inc(batch_pixels); + + y0 = y1; + } + } + } + + /// Stage 1: Generate camera rays for scanlines [y0, y1). + fn generate_camera_rays( + &mut self, + y0: i32, + y1: i32, + sample_index: u32, + pixel_bounds: &Bounds2i, + ) { + // For each pixel in the scanline range, generate a camera ray + // and push it to the ray queue. Also initialize the PixelSampleState. + for y in y0..y1 { + for x in pixel_bounds.p_min.x()..pixel_bounds.p_max.x() { + let p_pixel = Point2i::new(x, y); + + // TODO: proper sampler state per pixel/sample + // For now, use a simple approach + self.sampler + .start_pixel_sample(p_pixel, sample_index as i32, Some(0)); + + let lambda = SampledWavelengths::sample_visible(self.sampler.get1d()); + + let camera_sample = crate::core::sampler::get_camera_sample( + &mut self.sampler, + p_pixel, + &self.filter, + ); + + let Some(camera_ray) = self.camera.generate_ray(camera_sample, &lambda) else { + continue; + }; + + // Compute pixel index for this sample + let pixel_index = self.ray_queues[0].size(); + + // Initialize persistent pixel state + let pi = pixel_index as usize; + self.pixel_sample_state.l.set(pi, SampledSpectrum::new(0.0)); + self.pixel_sample_state.beta.set(pi, camera_ray.weight); + self.pixel_sample_state.lambda.set(pi, lambda); + self.pixel_sample_state + .r_u + .set(pi, SampledSpectrum::new(1.0)); + self.pixel_sample_state + .r_l + .set(pi, SampledSpectrum::new(1.0)); + self.pixel_sample_state.depth.set(pi, 0); + self.pixel_sample_state.specular_bounce.set(pi, 1); + self.pixel_sample_state.any_non_specular_bounces.set(pi, 0); + self.pixel_sample_state.eta_scale.set(pi, 1.0); + self.pixel_sample_state.p_film.set(pi, camera_sample.p_film); + self.pixel_sample_state + .filter_weight + .set(pi, camera_sample.filter_weight); + self.pixel_sample_state + .prev_intr_ctx + .set(pi, LightSampleContext::default()); + + // Push ray to queue + self.ray_queues[0].push(RayWorkItem { + ray_o: camera_ray.ray.o, + ray_d: camera_ray.ray.d, + ray_time: camera_ray.ray.time, + ray_medium: camera_ray.ray.medium, + pixel_index: pixel_index, + has_differentials: true, + differential: RayDifferential::default(), + }); + } + } + } + + /// Handle escaped rays — evaluate infinite lights. + fn handle_escaped_rays(&self) { + let n = self.escaped_ray_queue.size(); + for i in 0..n as usize { + let w = unsafe { self.escaped_ray_queue.storage.get(i) }; + + let mut l_contrib = SampledSpectrum::new(0.0); + + // Evaluate all infinite lights + for light_ptr in &self.infinite_lights { + let light = light_ptr.get().unwrap(); + let ray = crate::core::geometry::Ray::new(w.ray_o, w.ray_d, None, Ptr::null()); + let le = light.le(&ray, &w.lambda); + if le.is_black() { + continue; + } + + if w.depth == 0 || w.specular_bounce { + // No MIS for direct camera rays or specular bounces + l_contrib += w.beta * le / w.r_u.average(); + } else { + // MIS with light sampling + // TODO: compute light PDF for MIS weight + // For now, use unidirectional weight only + l_contrib += w.beta * le / w.r_u.average(); + } + } + + if !l_contrib.is_black() { + let pi = w.pixel_index as usize; + let mut l = self.pixel_sample_state.l.get(pi); + l += l_contrib; + self.pixel_sample_state.l.set(pi, l); + } + } + } + + /// Handle emissive intersections — area light contribution with MIS. + fn handle_emissive_intersections(&self) { + let n = self.hit_area_light_queue.size(); + for i in 0..n as usize { + let w = unsafe { self.hit_area_light_queue.storage.get(i) }; + + let light = w.area_light.get().unwrap(); + let le = light.l(w.p, w.n, w.uv, w.wo, &w.lambda); + if le.is_black() { + continue; + } + + let l_contrib = if w.depth == 0 || w.specular_bounce { + w.beta * le / w.r_u.average() + } else { + // MIS: combine BSDF and light sampling weights + // TODO: full MIS with light sampler PDF + w.beta * le / w.r_u.average() + }; + + if !l_contrib.is_black() { + let pi = w.pixel_index as usize; + let mut l = self.pixel_sample_state.l.get(pi); + l += l_contrib; + self.pixel_sample_state.l.set(pi, l); + } + } + } + + fn evaluate_materials_and_bsdfs(&mut self, depth: u32) { + self.evaluate_material_queue_impl(depth, false); + self.evaluate_material_queue_impl(depth, true); + } + + fn evaluate_material_queue_impl(&mut self, depth: u32, use_universal: bool) { + let queue = if use_universal { + &self.universal_eval_material_queue + } else { + &self.basic_eval_material_queue + }; + + let n = queue.size(); + let next = ((depth + 1) % 2) as usize; + + for i in 0..n as usize { + let w = unsafe { queue.storage.get(i) }; + let pi = w.pixel_index as usize; + + let lambda = self.pixel_sample_state.lambda.get(pi); + let beta = self.pixel_sample_state.beta.get(pi); + let any_non_specular = self.pixel_sample_state.any_non_specular_bounces.get(pi) != 0; + let eta_scale = self.pixel_sample_state.eta_scale.get(pi); + + let Some(material) = w.material.get() else { + continue; + }; + + let tex_eval = UniversalTextureEvaluator; + let ctx = MaterialEvalContext { + texture: TextureEvalContext { + p: w.p, + dpdx: Vector3f::zero(), + dpdy: Vector3f::zero(), + n: w.n, + uv: w.uv, + dudx: 0.0, + dudy: 0.0, + dvdx: 0.0, + dvdy: 0.0, + face_index: w.face_index, + }, + wo: w.wo, + ns: w.ns, + dpdus: w.dpdu, + }; + let mut bsdf = material.get_bsdf(&tex_eval, &ctx, &lambda); + + if bsdf.flags().is_empty() { + continue; + } + + if self.regularize && any_non_specular { + bsdf.regularize(); + } + + if depth >= self.max_depth { + continue; + } + + // Sample a light, compute contribution, + // push shadow ray with deferred visibility + if bsdf.flags().is_non_specular() { + let light_ctx = LightSampleContext { + pi: Point3fi::new_from_point(w.p), + n: w.n, + ns: w.ns, + }; + + if let Some(sampled_light) = self + .light_sampler + .sample_with_context(&light_ctx, self.sampler.get1d()) + { + if let Some(ls) = sampled_light.light.sample_li( + &light_ctx, + self.sampler.get2d(), + &lambda, + true, + ) { + if !ls.l.is_black() && ls.pdf > 0.0 { + let wi = ls.wi; + if let Some(f_val) = bsdf.f(w.wo, wi, TransportMode::Radiance) { + let f_cos = f_val * wi.abs_dot(w.ns.into()); + if !f_cos.is_black() { + let p_l = sampled_light.p * ls.pdf; + let l_d = if sampled_light.light.light_type().is_delta_light() { + beta * ls.l * f_cos / p_l + } else { + let p_b = bsdf.pdf(w.wo, wi, FArgs::default()); + let w_l = power_heuristic(1, p_l, 1, p_b); + beta * w_l * ls.l * f_cos / p_l + }; + + if !l_d.is_black() { + let ray_o = Ray::offset_origin( + &Point3fi::new_from_point(w.p), + &w.n, + &wi, + ); + let t_max = (1.0 - 1e-4) + * (Point3f::from(ls.p_light.p()) - ray_o).norm() + / wi.norm(); + + self.shadow_ray_queue.push(ShadowRayWorkItem { + ray_o, + ray_d: wi, + ray_time: w.time, + t_max, + lambda, + l_d, + pixel_index: w.pixel_index, + }); + } + } + } + } + } + } + } + + // Sample BSDF for next bounce + let wo = w.wo; + let Some(bs) = bsdf.sample_f( + wo, + self.sampler.get1d(), + self.sampler.get2d(), + FArgs::default(), + ) else { + continue; + }; + + let f_cos = bs.f * bs.wi.abs_dot(w.ns.into()); + if f_cos.is_black() || bs.pdf == 0.0 { + continue; + } + let new_beta = beta * f_cos / bs.pdf; + + let new_depth = depth + 1; + + // Russian roulette + if new_depth > 3 { + let rr_beta = new_beta.max_component_value(); + if rr_beta < 0.25 { + let q = (1.0 - rr_beta).max(0.0_f32); + if self.sampler.get1d() < q { + continue; + } + } + } + + let ray_o = Ray::offset_origin(&Point3fi::new_from_point(w.p), &w.n, &bs.wi); + + // Update PixelSampleState + self.pixel_sample_state.beta.set(pi, new_beta); + self.pixel_sample_state.depth.set(pi, new_depth); + self.pixel_sample_state + .specular_bounce + .set(pi, bs.is_specular() as u8); + self.pixel_sample_state + .any_non_specular_bounces + .set(pi, (any_non_specular || !bs.is_specular()) as u8); + self.pixel_sample_state.eta_scale.set( + pi, + if bs.is_transmissive() { + eta_scale * square(bs.eta) + } else { + eta_scale + }, + ); + self.pixel_sample_state.prev_intr_ctx.set( + pi, + LightSampleContext { + pi: Point3fi::new_from_point(w.p), + n: w.n, + ns: w.ns, + }, + ); + + // Push next bounce ray + self.ray_queues[next].push(RayWorkItem { + ray_o, + ray_d: bs.wi, + ray_time: w.time, + ray_medium: Ptr::null(), + pixel_index: w.pixel_index, + has_differentials: true, + differential: RayDifferential::default(), + }); + } + } + + fn update_film(&self, y0: i32, y1: i32, pixel_bounds: &Bounds2i) { + // The pixel_sample_state indices map to rays generated in + // generate_camera_rays. We need to walk the same pixel order + // and read back the accumulated L values. + let mut pi = 0usize; + for y in y0..y1 { + for x in pixel_bounds.p_min.x()..pixel_bounds.p_max.x() { + let l = self.pixel_sample_state.l.get(pi); + let lambda = self.pixel_sample_state.lambda.get(pi); + let filter_weight = self.pixel_sample_state.filter_weight.get(pi); + let p_film = self.pixel_sample_state.p_film.get(pi); + + // Add sample to film + self.film.add_sample( + Point2i::new(p_film.x() as i32, p_film.y() as i32), + l, + &lambda, + Some(&crate::core::film::VisibleSurface::default()), + filter_weight, + ); + + pi += 1; + } + } + } +} diff --git a/src/wavefront/mod.rs b/src/wavefront/mod.rs index 12b9be2..8eb82c1 100644 --- a/src/wavefront/mod.rs +++ b/src/wavefront/mod.rs @@ -1 +1,4 @@ +pub mod aggregate; pub mod integrator; + +pub use aggregate::CpuAggregate;