diff --git a/.gitignore b/.gitignore index e2b964b..94a9c6f 100644 --- a/.gitignore +++ b/.gitignore @@ -15,7 +15,9 @@ tests/ scenes/ compile.sh output/ -*.md !README.md !INSTALL.md docs/ +GTAGS +GPATH +GRTAGS diff --git a/shared/src/core/interaction.rs b/shared/src/core/interaction.rs index 5aca6d4..0d818f1 100644 --- a/shared/src/core/interaction.rs +++ b/shared/src/core/interaction.rs @@ -18,7 +18,7 @@ use crate::core::texture::{FloatTexture, UniversalTextureEvaluator}; use crate::core::{LightIdx, MaterialIdx}; use crate::spectra::{SampledSpectrum, SampledWavelengths}; use crate::utils::math::{clamp, difference_of_products, square}; -use crate::{GVec, Ptr, Float}; +use crate::{Ptr, Float}; use enum_dispatch::enum_dispatch; #[repr(C)] @@ -243,7 +243,7 @@ impl SurfaceInteraction { &self, w: Vector3f, lambda: &SampledWavelengths, - lights: &GVec, + lights: &[Light], ) -> SampledSpectrum { if self.area_light.is_none() { return SampledSpectrum::new(0.); @@ -253,7 +253,7 @@ impl SurfaceInteraction { } pub fn compute_differentials(&mut self, r: &Ray, camera: &Camera, samples_per_pixel: i32) { - let computed = if !r.has_differentials { + let computed = if r.has_differentials { let diff = r.differential; let dot_rx = self.common.n.dot(diff.rx_direction.into()); let dot_ry = self.common.n.dot(diff.ry_direction.into()); @@ -341,7 +341,7 @@ impl SurfaceInteraction { let new_ray = Ray::spawn(&self.pi(), &self.n(), ray.time, ray.d); ray.o = new_ray.o; // Skipping other variables, since they should not change when passing through surface - if !ray.has_differentials { + if ray.has_differentials { let mut diff = ray.differential; diff.rx_origin += diff.rx_direction * t; diff.ry_origin += diff.ry_direction * t; diff --git a/shared/src/lights/diffuse.rs b/shared/src/lights/diffuse.rs index f704c06..48375a9 100644 --- a/shared/src/lights/diffuse.rs +++ b/shared/src/lights/diffuse.rs @@ -37,12 +37,12 @@ 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 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() { @@ -73,7 +73,12 @@ impl LightTrait for DiffuseAreaLight { _allow_incomplete_pdf: bool, ) -> Option { let shape_ctx = ShapeSampleContext::new(ctx.pi, ctx.n, ctx.ns, 0.0); + let ss = self.shape.sample_from_context(&shape_ctx, u)?; + if ss.pdf == 0.0 || (ss.intr.p() - ctx.p()).norm_squared() == 0.0 { + return None; + } + let mut intr = ss.intr; intr.set_medium_interface(self.base.medium_interface); let p = intr.p(); @@ -108,9 +113,10 @@ impl LightTrait for DiffuseAreaLight { w: Vector3f, lambda: &SampledWavelengths, ) -> SampledSpectrum { - if self.two_sided && n.dot(w.into()) < 0. { + if !self.two_sided && n.dot(w.into()) < 0. { return SampledSpectrum::new(0.); } + let intr = Interaction::Surface(SurfaceInteraction::new_minimal( Point3fi::new_from_point(p), uv, @@ -149,7 +155,8 @@ impl LightTrait for DiffuseAreaLight { rgb[c] = self.image.get_channel(Point2i::new(x, y), c as i32); } - l += RGBIlluminantSpectrum::new(&self.colorspace, rgb.clamp_zero()).sample(&lambda); + l += RGBIlluminantSpectrum::new(&self.colorspace, rgb.clamp_zero()) + .sample(&lambda); } } l *= self.scale / (self.image.resolution().x() * self.image.resolution().y()) as Float; @@ -162,7 +169,7 @@ impl LightTrait for DiffuseAreaLight { #[cfg(not(target_os = "cuda"))] fn preprocess(&mut self, _scene_bounds: &Bounds3f) { - return + return; } #[cfg(not(target_os = "cuda"))] diff --git a/shared/src/shapes/bilinear.rs b/shared/src/shapes/bilinear.rs index 8433e50..30dadb4 100644 --- a/shared/src/shapes/bilinear.rs +++ b/shared/src/shapes/bilinear.rs @@ -393,7 +393,7 @@ impl BilinearPatchShape { let Some(normals) = shading_normals else { return; }; - let n00 = normals[1]; + let n00 = normals[0]; let n10 = normals[1]; let n01 = normals[2]; let n11 = normals[3]; diff --git a/shared/src/shapes/triangle.rs b/shared/src/shapes/triangle.rs index 6c8484e..c002719 100644 --- a/shared/src/shapes/triangle.rs +++ b/shared/src/shapes/triangle.rs @@ -306,7 +306,7 @@ impl TriangleShape { isect.common.n = ng; isect.shading.n = ng; - if !self.mesh.p.is_empty() || !self.mesh.s.is_empty() { + if !self.mesh.n.is_empty() || !self.mesh.s.is_empty() { self.compute_shading_geometry(&mut isect, &ti, uv, dpdu, determinant, degenerate); } isect @@ -455,13 +455,8 @@ impl ShapeTrait for TriangleShape { fn sample_from_context(&self, ctx: &ShapeSampleContext, mut u: Point2f) -> Option { let [p0, p1, p2] = self.get_points(); - - let (b, tri_pdf) = sample_spherical_triangle(&[p0, p1, p2], ctx.p(), u)?; - if tri_pdf == 0. { - return None; - } - let solid_angle = self.solid_angle(ctx.p()); + if solid_angle < Self::MIN_SPHERICAL_SAMPLE_AREA || solid_angle > Self::MAX_SPHERICAL_SAMPLE_AREA { @@ -497,6 +492,11 @@ impl ShapeTrait for TriangleShape { pdf = bilinear_pdf(u, &w); } + let (b, tri_pdf) = sample_spherical_triangle(&[p0, p1, p2], ctx.p(), u)?; + if tri_pdf == 0. { + return None; + } + let p0_v = Vector3f::from(p0); let p1_v = Vector3f::from(p1); let p2_v = Vector3f::from(p2); diff --git a/shared/src/textures/image.rs b/shared/src/textures/image.rs index 8c5612c..2801ad2 100644 --- a/shared/src/textures/image.rs +++ b/shared/src/textures/image.rs @@ -8,6 +8,13 @@ use crate::spectra::{ }; use crate::utils::Ptr; use crate::Float; +use core::sync::atomic::{AtomicU32, Ordering}; + +pub static DIAG_IMG_COUNT: AtomicU32 = AtomicU32::new(0); +pub static DIAG_IMG_SCALE_BITS: AtomicU32 = AtomicU32::new(0); +pub static DIAG_IMG_PIXEL0_BITS: AtomicU32 = AtomicU32::new(0); +pub static DIAG_IMG_RGB0_BITS: AtomicU32 = AtomicU32::new(0); +pub static DIAG_IMG_RESULT0_BITS: AtomicU32 = AtomicU32::new(0); /* GPU heavy code, dont know if this will ever work the way Im doing things. * Leaving it here isolated, for careful handling */ @@ -49,16 +56,22 @@ impl SpectrumImageTexture { let wrap = WrapMode2D { uv: [self.wrap_mode; 2], }; + let pixel0 = image.bilerp_channel_with_wrap(c.st, 0, wrap); let rgb = if image.n_channels == 1 { - let v = image.bilerp_channel_with_wrap(c.st, 0, wrap); - RGB::new(v, v, v) + RGB::new(pixel0, pixel0, pixel0) } else { RGB::new( - image.bilerp_channel_with_wrap(c.st, 0, wrap), + pixel0, image.bilerp_channel_with_wrap(c.st, 1, wrap), image.bilerp_channel_with_wrap(c.st, 2, wrap), ) }; + let n = DIAG_IMG_COUNT.fetch_add(1, Ordering::Relaxed); + if n < 10 { + DIAG_IMG_SCALE_BITS.store(self.scale.to_bits(), Ordering::Relaxed); + DIAG_IMG_PIXEL0_BITS.store(pixel0.to_bits(), Ordering::Relaxed); + DIAG_IMG_RGB0_BITS.store((rgb[0] as f32).to_bits(), Ordering::Relaxed); + } let mut rgb = rgb * self.scale; if self.invert { rgb = (RGB::new(1.0, 1.0, 1.0) - rgb); @@ -68,11 +81,15 @@ impl SpectrumImageTexture { .color_space .get() .expect("color_space must not be null"); - match self.spectrum_type { + let result = match self.spectrum_type { SpectrumType::Unbounded => RGBUnboundedSpectrum::new(cs, rgb).sample(lambda), SpectrumType::Albedo => RGBAlbedoSpectrum::new(cs, rgb.clamp(0.0, 1.0)).sample(lambda), _ => RGBIlluminantSpectrum::new(cs, rgb).sample(lambda), + }; + if n < 10 { + DIAG_IMG_RESULT0_BITS.store(result[0].to_bits(), Ordering::Relaxed); } + result } } diff --git a/shared/src/utils/math.rs b/shared/src/utils/math.rs index f906714..928dbf4 100644 --- a/shared/src/utils/math.rs +++ b/shared/src/utils/math.rs @@ -4,7 +4,7 @@ use crate::core::pbrt::{Float, FloatBitOps, FloatBits, ONE_MINUS_EPSILON, PI, PI use crate::utils::gpu_array_from_fn; use crate::utils::hash::{hash_buffer, mix_bits}; use crate::utils::sobol::{SOBOL_MATRICES_32, VDC_SOBOL_MATRICES, VDC_SOBOL_MATRICES_INV}; -use crate::{gvec, gvec_with_capacity, GVec, Ptr}; +use crate::{GVec, Ptr, gvec, gvec_with_capacity}; use core::fmt::{self, Display, Write}; use core::iter::{Product, Sum}; use core::mem; @@ -79,7 +79,7 @@ where { let cd: V = d.mul_add(c, V::zero()); let diff: V = b.mul_add(a, -cd); - let err: V = d.mul_add(-c, cd); + let err: V = d.mul_add(-c, cd); diff + err } @@ -139,7 +139,7 @@ pub fn windowed_sinc(x: Float, radius: Float, tau: Float) -> Float { return 0.; } - if x < 1e-5 { + if x.abs() < 1e-5 { 1.0 } else { sinc(x) * sinc(x / tau) @@ -545,7 +545,7 @@ pub fn next_float_up(v: Float) -> Float { if v >= 0.0 { ui = ui.wrapping_add(1); } else { - ui.wrapping_sub(1); + ui = ui.wrapping_sub(1); } bits_to_float(ui) } @@ -1505,11 +1505,7 @@ where } let det: T = (0..N).map(|i| lum[i][i]).product(); - if parity < 0 { - -det - } else { - det - } + if parity < 0 { -det } else { det } } } } @@ -1600,7 +1596,7 @@ mod tests { let m = SquareMatrix { m: [[1.0, 2.0], [2.0, 4.0]], }; // Determinant is 0 - assert!(m.inverse().is_ok()); + assert!(m.inverse().is_none()); } #[test] diff --git a/src/core/render.rs b/src/core/render.rs index 2fc78ee..c6f10b1 100644 --- a/src/core/render.rs +++ b/src/core/render.rs @@ -16,7 +16,7 @@ use shared::Float; pub fn render_scene(scene: &BasicScene, arena: &Arena) -> Result<()> { let media = scene.create_media(); let textures = scene.create_textures(arena); - let (named_materials, materials) = scene.create_materials(&textures, arena)?; + let (named_materials, materials, _default_mtl) = scene.create_materials(&textures, arena)?; let (lights, al_map) = scene.create_lights(&textures, &media, arena); let _have_scattering = { @@ -30,10 +30,8 @@ pub fn render_scene(scene: &BasicScene, arena: &Arena) -> Result<()> { .any(|sh| !sh.inside_medium.is_empty() || !sh.outside_medium.is_empty()) }; - let aggregate = scene.create_aggregate(&textures, &named_materials, &materials, al_map, &media, arena); - - let mut all_lights = lights; - all_lights.extend(area_lights); + let aggregate = scene.create_aggregate(&textures, &named_materials, &materials, &al_map, &media, arena); + let all_lights = lights; let camera = scene.get_camera().unwrap(); let _film = camera.get_film(); @@ -114,6 +112,7 @@ pub fn render_scene(scene: &BasicScene, arena: &Arena) -> Result<()> { sampler.clone(), aggregate.clone(), all_lights, + materials, arena, ); render( diff --git a/src/core/scene/scene.rs b/src/core/scene/scene.rs index bf342fa..c703a2d 100644 --- a/src/core/scene/scene.rs +++ b/src/core/scene/scene.rs @@ -483,9 +483,8 @@ impl BasicScene { &self, textures: &NamedTextures, arena: &Arena, - ) -> Result<(HashMap, Vec)> { + ) -> Result<(HashMap, Vec, MaterialIdx)> { let mut state = self.material_state.lock(); - // Finish async normal map loads let finished: Vec<_> = state.normal_map_jobs.drain().collect(); for (filename, job) in finished { @@ -499,8 +498,10 @@ impl BasicScene { } } - // Named materials - let mut named_materials: HashMap = HashMap::new(); + let mut materials: Vec = Vec::new(); + let mut named_materials: HashMap = HashMap::new(); + // Value map for resolving named sub-materials (e.g. mix) during creation. + let mut named_values: HashMap = HashMap::new(); for (name, entity) in &state.named_materials { if named_materials.contains_key(name) { @@ -511,29 +512,29 @@ impl BasicScene { ); continue; } - let mat_type = entity.parameters.get_one_string("type", "")?; if mat_type.is_empty() { log::error!("{}: missing material type for '{}'", entity.loc, name); continue; } - let normal_map = self.get_normal_map(&state, &entity.parameters)?; let tex_dict = TextureParameterDictionary::new( Arc::new(entity.parameters.clone()), Some(textures), ); - match Material::create( &mat_type, &tex_dict, normal_map, - &named_materials, + &named_values, // value map, not index map entity.loc.clone(), arena, ) { Ok(mat) => { - named_materials.insert(name.clone(), mat); + let idx = MaterialIdx(materials.len() as u32); + materials.push(mat); + named_values.insert(name.clone(), mat); + named_materials.insert(name.clone(), idx); } Err(e) => { log::error!( @@ -546,37 +547,37 @@ impl BasicScene { } } - // Indexed materials - let materials: Vec = state - .materials - .iter() - .map(|entity| { - let result: Result = (|| { - let normal_map = self.get_normal_map(&state, &entity.parameters)?; - let tex_dict = TextureParameterDictionary::new( - entity.parameters.clone().into(), - Some(textures), - ); - Material::create( - &entity.name, - &tex_dict, - normal_map, - &named_materials, - entity.loc.clone(), - arena, - ) - })(); - match result { - Ok(mat) => mat, - Err(e) => { - log::error!("{}: failed to create material: {}", entity.loc, e); - crate::core::material::default_diffuse_material(arena) - } + // Indexed (anonymous) materials, appended after named ones. + for entity in &state.materials { + let result: Result = (|| { + let normal_map = self.get_normal_map(&state, &entity.parameters)?; + let tex_dict = TextureParameterDictionary::new( + entity.parameters.clone().into(), + Some(textures), + ); + Material::create( + &entity.name, + &tex_dict, + normal_map, + &named_values, + entity.loc.clone(), + arena, + ) + })(); + let mat = match result { + Ok(mat) => mat, + Err(e) => { + log::error!("{}: failed to create material: {}", entity.loc, e); + crate::core::material::default_diffuse_material(arena) } - }) - .collect(); + }; + materials.push(mat); + } - Ok((named_materials, materials)) + let default_mtl = MaterialIdx(materials.len() as u32); + materials.push(crate::core::material::default_diffuse_material(arena)); + + Ok((named_materials, materials, default_mtl)) } pub fn create_media(&self) -> HashMap> { @@ -810,7 +811,8 @@ impl BasicScene { camera: Arc, sampler: Arc, aggregate: Arc, - lights: Vec>, + lights: Vec, + materials: Vec, arena: &Arena, ) -> PathIntegrator { let integrator_entity = self.integrator.lock().clone().unwrap(); @@ -823,6 +825,7 @@ impl BasicScene { sampler, aggregate, lights, + materials, PathConfig::FULL, arena, ) diff --git a/src/integrators/base.rs b/src/integrators/base.rs index c0e9b53..babc6e0 100644 --- a/src/integrators/base.rs +++ b/src/integrators/base.rs @@ -4,6 +4,7 @@ use shared::core::interaction::{Interaction, InteractionTrait}; use shared::core::light::{Light, LightTrait}; use shared::core::primitive::{Primitive, PrimitiveTrait}; use shared::core::shape::ShapeIntersection; +use shared::core::LightIdx; use shared::lights::sampler::{LightSampler, LightSamplerTrait}; use shared::spectra::SampledWavelengths; use shared::utils::sampling::power_heuristic; @@ -13,18 +14,16 @@ use std::sync::Arc; #[derive(Clone, Debug)] pub struct IntegratorBase { pub aggregate: Arc, - pub lights: Vec>, - pub infinite_lights: Vec>, + pub lights: Vec, + pub infinite_lights: Vec, } impl IntegratorBase { - pub fn new(aggregate: Arc, mut lights: Vec>) -> Self { + pub fn new(aggregate: Arc, mut lights: Vec) -> Self { let scene_bounds = aggregate.bounds(); for light in &mut lights { - Arc::get_mut(light) - .expect("Light has multiple owners during setup") - .preprocess(&scene_bounds); + light.preprocess(&scene_bounds); } println!( @@ -36,10 +35,11 @@ impl IntegratorBase { println!(" light[{}]: type={:?}", i, l.light_type()); } - let infinite_lights = lights + let infinite_lights: Vec = lights .iter() - .filter(|light| light.light_type().is_infinite()) - .cloned() + .enumerate() + .filter(|(_, l)| l.light_type().is_infinite()) + .map(|(i, _)| LightIdx(i as u32)) .collect(); Self { @@ -72,7 +72,8 @@ impl IntegratorBase { light_sampler: Option<&LightSampler>, use_mis: bool, ) { - for light in &self.infinite_lights { + for &idx in &self.infinite_lights { + let light = &self.lights[idx.0 as usize]; let le = light.le(ray, lambda); if le.is_black() { continue; @@ -81,7 +82,7 @@ impl IntegratorBase { if state.depth == 0 || state.specular_bounce || !use_mis { state.l += state.beta * le; } else if let Some(sampler) = light_sampler { - let p_l = sampler.pmf_with_context(&state.prev_ctx, light) + let p_l = sampler.pmf_with_context(&state.prev_ctx, idx) * 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; diff --git a/src/integrators/mod.rs b/src/integrators/mod.rs index 9f08671..a9ab2c0 100644 --- a/src/integrators/mod.rs +++ b/src/integrators/mod.rs @@ -13,6 +13,7 @@ use shared::core::camera::Camera; use shared::core::film::VisibleSurface; use shared::core::geometry::{Point2i, Ray}; use shared::core::light::Light; +use shared::core::material::Material; use shared::core::primitive::Primitive; use shared::core::sampler::Sampler; use shared::spectra::{SampledSpectrum, SampledWavelengths}; @@ -47,7 +48,8 @@ pub trait CreateIntegrator { camera: Arc, sampler: Arc, aggregate: Arc, - lights: Vec>, + lights: Vec, + materials: Vec, config: PathConfig, arena: &Arena, ) -> Result; @@ -59,14 +61,15 @@ impl CreateIntegrator for PathIntegrator { camera: Arc, _sampler: Arc, aggregate: Arc, - lights: Vec>, + lights: Vec, + materials: Vec, config: PathConfig, arena: &Arena, ) -> Result { let _max_depth = parameters.get_one_int("maxdepth", 5)?; let _regularize = parameters.get_one_bool("regularize", false)?; let light_sampler = create_light_sampler("power", &lights, arena); - let integrator = PathIntegrator::new(aggregate, lights, camera, light_sampler, config); + let integrator = PathIntegrator::new(aggregate, lights, camera, light_sampler, config, materials); Ok(integrator) } } diff --git a/src/integrators/path.rs b/src/integrators/path.rs index 8fe4a59..32d5e71 100644 --- a/src/integrators/path.rs +++ b/src/integrators/path.rs @@ -12,6 +12,7 @@ use shared::core::geometry::{Point2i, Ray, Vector3f, VectorLike}; use shared::core::interaction::{Interaction, InteractionTrait, SurfaceInteraction}; use shared::core::light::LightTrait; use shared::core::light::{Light, LightSampleContext}; +use shared::core::material::Material; use shared::core::primitive::Primitive; use shared::core::sampler::{Sampler, SamplerTrait}; use shared::lights::sampler::LightSampler; @@ -68,6 +69,7 @@ pub struct PathIntegrator { camera: Arc, sampler: LightSampler, config: PathConfig, + materials: Vec, } unsafe impl Send for PathIntegrator {} @@ -76,10 +78,11 @@ unsafe impl Sync for PathIntegrator {} impl PathIntegrator { pub fn new( aggregate: Arc, - lights: Vec>, + lights: Vec, camera: Arc, sampler: LightSampler, config: PathConfig, + materials: Vec, ) -> Self { let base = IntegratorBase::new(aggregate, lights); Self { @@ -87,6 +90,7 @@ impl PathIntegrator { camera, sampler, config, + materials, } } @@ -104,7 +108,9 @@ impl PathIntegrator { return SampledSpectrum::zero(); }; - let Some(ls) = sampled.light.sample_li(&ctx, sampler.get2d(), lambda, true) else { + let light = &self.base.lights[sampled.light.0 as usize]; + + let Some(ls) = light.sample_li(&ctx, sampler.get2d(), lambda, true) else { return SampledSpectrum::zero(); }; @@ -133,7 +139,7 @@ impl PathIntegrator { let p_l = sampled.p * ls.pdf; - if !self.config.use_mis || sampled.light.light_type().is_delta_light() { + if !self.config.use_mis || light.light_type().is_delta_light() { ls.l * f / p_l } else { let p_b = bsdf.pdf(wo, wi, FArgs::default()); @@ -228,22 +234,22 @@ impl RayIntegratorTrait for PathIntegrator { let isect = &mut si.intr; // Emission from hit surface - let le = isect.le(-ray.d, lambda); + let le = isect.le(-ray.d, lambda, &self.base.lights); if !le.is_black() { if state.depth == 0 || state.specular_bounce { state.l += state.beta * le; - } else if self.config.use_mis - && !isect.area_light.is_none() { - 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; - } + } else if self.config.use_mis && !isect.area_light.is_none() { + let idx = isect.area_light; + let light = &self.base.lights[idx.0 as usize]; + let p_l = self.sampler.pmf_with_context(&state.prev_ctx, idx) + * 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 - let Some(mut bsdf) = isect.get_bsdf(&ray, lambda, &self.camera, sampler) else { + let Some(mut bsdf) = isect.get_bsdf(&ray, lambda, &self.camera, sampler, &self.materials) else { state.specular_bounce = true; isect.skip_intersection(&mut ray, t_hit); continue; diff --git a/src/integrators/pipeline.rs b/src/integrators/pipeline.rs index ea3102d..eaf8877 100644 --- a/src/integrators/pipeline.rs +++ b/src/integrators/pipeline.rs @@ -248,14 +248,6 @@ pub fn evaluate_pixel_sample( eprintln!(" camera_sample.p_film: {:?}", camera_sample.p_film); } - eprintln!( - "PATH p=({},{}) l={:?} lambda={:?} fw={}", - pixel.x(), - pixel.y(), - l, - lambda, - camera_sample.filter_weight, - ); film.add_sample( pixel, l, diff --git a/src/textures/image.rs b/src/textures/image.rs index 45494b4..cd69b3d 100644 --- a/src/textures/image.rs +++ b/src/textures/image.rs @@ -131,12 +131,26 @@ impl SpectrumImageTexture { impl SpectrumTextureTrait for SpectrumImageTexture { fn evaluate(&self, ctx: &TextureEvalContext, lambda: &SampledWavelengths) -> SampledSpectrum { + use std::sync::atomic::{AtomicU32, Ordering}; + static PATH_IMG_COUNT: AtomicU32 = AtomicU32::new(0); + let pn = PATH_IMG_COUNT.fetch_add(1, Ordering::Relaxed); + let mut c = self.base.mapping.map(ctx); c.st[1] = 1. - c.st[1]; let dst0 = Vector2f::new(c.dsdx, c.dtdx); let dst1 = Vector2f::new(c.dsdy, c.dtdy); - let rgb_unclamp = self.base.scale * self.base.mipmap.filter::(c.st, dst0, dst1); + let raw_rgb = self.base.mipmap.filter::(c.st, dst0, dst1); + let rgb_unclamp = self.base.scale * raw_rgb; let rgb = RGB::clamp_zero(&rgb_unclamp); + + if pn < 5 { + eprintln!("PATH_IMG[{pn}] scale={:.6} raw_rgb[0]={:.6} rgb_after_scale[0]={:.6} \ + st={:?} dst0={:?} dst1={:?} has_cs={}", + self.base.scale, raw_rgb[0], rgb_unclamp[0], + c.st, dst0, dst1, + self.base.mipmap.get_rgb_colorspace().is_some()); + } + if let Some(cs) = self.base.mipmap.get_rgb_colorspace() { match self.spectrum_type { SpectrumType::Unbounded => { @@ -148,8 +162,11 @@ impl SpectrumTextureTrait for SpectrumImageTexture { _ => return RGBIlluminantSpectrum::new(&cs, rgb).sample(lambda), } } - assert!(rgb[0] == rgb[1] && rgb[1] == rgb[2]); - SampledSpectrum::new(rgb[0]) + let result = SampledSpectrum::new(rgb[0]); + if pn < 5 { + eprintln!("PATH_IMG[{pn}] no-cs branch result[0]={:.6}", result[0]); + } + result } } diff --git a/src/wavefront/aggregate.rs b/src/wavefront/aggregate.rs index 4e20f7e..7689a23 100644 --- a/src/wavefront/aggregate.rs +++ b/src/wavefront/aggregate.rs @@ -1,5 +1,8 @@ use crate::globals::get_options; use log::debug; +use std::sync::atomic::{AtomicU32, Ordering}; + +pub static DIAG_SHADOW_UNOCCLUDED: AtomicU32 = AtomicU32::new(0); use rayon::prelude::*; use shared::core::geometry::{Bounds3f, Ray, VectorLike}; use shared::core::interaction::{InteractionTrait, SurfaceInteraction}; @@ -113,7 +116,7 @@ impl WavefrontAggregate for CpuAggregate { ); } - eval_q.push(MaterialEvalWorkItem { + let item = MaterialEvalWorkItem { p: intr.pi(), n: intr.n(), ns: intr.shading.n, @@ -137,7 +140,23 @@ impl WavefrontAggregate for CpuAggregate { dpdvs: intr.shading.dpdv, dndus: intr.shading.dndu, dndvs: intr.shading.dndv, - }); + }; + if let Some(slot) = eval_q.push(item) { + if slot < 10 { + eprintln!( + "ENQUEUE[{slot}] pixel={:?} depth={} \ + p={:?} n={:?} ns={:?} \ + dpdu={:?} dpdv={:?} \ + dpdus={:?} dpdvs={:?} \ + uv={:?} material={:?} area_light={:?} face_index={}", + item.pixel_index, item.depth, + item.p, item.n, item.ns, + item.dpdu, item.dpdv, + item.dpdus, item.dpdvs, + item.uv, item.material, item.area_light, item.face_index, + ); + } + } }); } @@ -156,6 +175,15 @@ impl WavefrontAggregate for CpuAggregate { if !self.aggregate.intersect_p(&ray, Some(work.t_max)) { let pi = work.pixel_index as usize; let ld = work.l_d / (work.r_u + work.r_l).average(); + let n = DIAG_SHADOW_UNOCCLUDED.fetch_add(1, Ordering::Relaxed); + if n < 10 { + eprintln!( + "SHADOW_UNOCCLUDED[{n}] pixel={} l_d={:?} r_u={:?} r_l={:?} \ + denom={:.6} ld={:?}", + pi, work.l_d, work.r_u, work.r_l, + (work.r_u + work.r_l).average(), ld + ); + } let mut l = pixel_sample_state.l.get(pi); l += ld; pixel_sample_state.l.set(pi, l); diff --git a/src/wavefront/integrator.rs b/src/wavefront/integrator.rs index 9c09ca5..3f6fba6 100644 --- a/src/wavefront/integrator.rs +++ b/src/wavefront/integrator.rs @@ -29,9 +29,26 @@ use shared::utils::soa::{SoA, SoAAllocator, WorkQueue}; use shared::wavefront::workitems::*; use shared::wavefront::{WavefrontAggregate, WavefrontPathIntegrator, WavefrontRenderer}; use shared::{gvec, gvec_from_slice, GVec, Ptr, SHADOW_EPSILON}; +use shared::textures::image::{ + DIAG_IMG_COUNT, DIAG_IMG_SCALE_BITS, DIAG_IMG_PIXEL0_BITS, + DIAG_IMG_RGB0_BITS, DIAG_IMG_RESULT0_BITS, +}; use std::ops::{Deref, DerefMut}; +use std::sync::atomic::{AtomicU32, Ordering}; use std::sync::Arc; +static DIAG_EVAL_ENTER: AtomicU32 = AtomicU32::new(0); +static DIAG_BSDF_EMPTY: AtomicU32 = AtomicU32::new(0); +static DIAG_NON_SPECULAR_SKIP: AtomicU32 = AtomicU32::new(0); +static DIAG_SAMPLE_LIGHT_NONE: AtomicU32 = AtomicU32::new(0); +static DIAG_SAMPLE_LI_NONE: AtomicU32 = AtomicU32::new(0); +static DIAG_LS_L_BLACK: AtomicU32 = AtomicU32::new(0); +static DIAG_LS_PDF_ZERO: AtomicU32 = AtomicU32::new(0); +static DIAG_F_NONE: AtomicU32 = AtomicU32::new(0); +static DIAG_F_BLACK: AtomicU32 = AtomicU32::new(0); +static DIAG_SHADOW_PUSH: AtomicU32 = AtomicU32::new(0); +static DIAG_NEE_D0_PRINT: AtomicU32 = AtomicU32::new(0); + pub struct CpuWavefrontRenderer(pub WavefrontPathIntegrator); impl Deref for CpuWavefrontRenderer { @@ -84,7 +101,7 @@ where } } - let cpu_aggregate = CpuAggregate::new(*aggregate); + let cpu_aggregate = CpuAggregate::new(*aggregate, &materials); let bounds = aggregate.bounds(); for light in &mut lights { @@ -210,6 +227,30 @@ impl CpuWavefrontRenderer { &self.shadow_ray_queue, &self.pixel_sample_state, ); + + if sample_index == 0 && y0 == pixel_bounds.p_min.y() { + eprintln!("=== DIAG s=0 y0={} depth={} ===", y0, depth); + eprintln!(" eval_enter={}", DIAG_EVAL_ENTER.load(Ordering::Relaxed)); + eprintln!(" bsdf_empty={}", DIAG_BSDF_EMPTY.load(Ordering::Relaxed)); + eprintln!(" non_specular_skip={}", DIAG_NON_SPECULAR_SKIP.load(Ordering::Relaxed)); + eprintln!(" sample_light_none={}", DIAG_SAMPLE_LIGHT_NONE.load(Ordering::Relaxed)); + eprintln!(" sample_li_none={}", DIAG_SAMPLE_LI_NONE.load(Ordering::Relaxed)); + eprintln!(" ls_l_black={}", DIAG_LS_L_BLACK.load(Ordering::Relaxed)); + eprintln!(" ls_pdf_zero={}", DIAG_LS_PDF_ZERO.load(Ordering::Relaxed)); + eprintln!(" f_none={}", DIAG_F_NONE.load(Ordering::Relaxed)); + eprintln!(" f_black={}", DIAG_F_BLACK.load(Ordering::Relaxed)); + eprintln!(" shadow_push={}", DIAG_SHADOW_PUSH.load(Ordering::Relaxed)); + eprintln!(" shadow_unoccluded={}", super::aggregate::DIAG_SHADOW_UNOCCLUDED.load(Ordering::Relaxed)); + let img_n = DIAG_IMG_COUNT.load(Ordering::Relaxed); + if img_n > 0 { + let scale = f32::from_bits(DIAG_IMG_SCALE_BITS.load(Ordering::Relaxed)); + let pixel0 = f32::from_bits(DIAG_IMG_PIXEL0_BITS.load(Ordering::Relaxed)); + let rgb0 = f32::from_bits(DIAG_IMG_RGB0_BITS.load(Ordering::Relaxed)); + let result0 = f32::from_bits(DIAG_IMG_RESULT0_BITS.load(Ordering::Relaxed)); + eprintln!(" img_tex_calls={} scale={:.6} pixel0={:.6} rgb0_pre_scale={:.6} result[0]={:.6}", + img_n, scale, pixel0, rgb0, result0); + } + } } self.update_film(y0, y1, &pixel_bounds); let batch_pixels = @@ -219,6 +260,18 @@ impl CpuWavefrontRenderer { y0 = y1; } } + eprintln!("=== NEE DIAG COUNTS ==="); + eprintln!("eval_enter={}", DIAG_EVAL_ENTER.load(Ordering::Relaxed)); + eprintln!("bsdf_empty={}", DIAG_BSDF_EMPTY.load(Ordering::Relaxed)); + eprintln!("non_specular_skip={}", DIAG_NON_SPECULAR_SKIP.load(Ordering::Relaxed)); + eprintln!("sample_light_none={}", DIAG_SAMPLE_LIGHT_NONE.load(Ordering::Relaxed)); + eprintln!("sample_li_none={}", DIAG_SAMPLE_LI_NONE.load(Ordering::Relaxed)); + eprintln!("ls_l_black={}", DIAG_LS_L_BLACK.load(Ordering::Relaxed)); + eprintln!("ls_pdf_zero={}", DIAG_LS_PDF_ZERO.load(Ordering::Relaxed)); + eprintln!("f_none={}", DIAG_F_NONE.load(Ordering::Relaxed)); + eprintln!("f_black={}", DIAG_F_BLACK.load(Ordering::Relaxed)); + eprintln!("shadow_push={}", DIAG_SHADOW_PUSH.load(Ordering::Relaxed)); + eprintln!("shadow_unoccluded={}", super::aggregate::DIAG_SHADOW_UNOCCLUDED.load(Ordering::Relaxed)); } fn generate_camera_rays( @@ -404,6 +457,21 @@ impl CpuWavefrontRenderer { (0..n as usize).into_par_iter().for_each(|i| { let w = unsafe { queue.storage.get(i) }; + if i < 10 { + eprintln!( + "DEQUEUE[{i}] pixel={:?} depth={} \ + p={:?} n={:?} ns={:?} \ + dpdu={:?} dpdv={:?} \ + dpdus={:?} dpdvs={:?} \ + uv={:?} material={:?} area_light={:?} face_index={}", + w.pixel_index, w.depth, + w.p, w.n, w.ns, + w.dpdu, w.dpdv, + w.dpdus, w.dpdvs, + w.uv, w.material, w.area_light, w.face_index, + ); + } + DIAG_EVAL_ENTER.fetch_add(1, Ordering::Relaxed); if w.material.is_none() { return; } @@ -445,6 +513,7 @@ impl CpuWavefrontRenderer { } if bsdf.flags().is_empty() { + DIAG_BSDF_EMPTY.fetch_add(1, Ordering::Relaxed); return; } if regularize && w.any_non_specular_bounces { @@ -513,6 +582,9 @@ impl CpuWavefrontRenderer { // Direct lighting let flags = bsdf.flags(); + if !flags.is_non_specular() { + DIAG_NON_SPECULAR_SKIP.fetch_add(1, Ordering::Relaxed); + } if flags.is_non_specular() { let mut light_ctx = LightSampleContext { pi: w.p, @@ -529,24 +601,33 @@ impl CpuWavefrontRenderer { let Some(sampled_light) = light_sampler.sample_with_context(&light_ctx, rs.direct.uc) else { + DIAG_SAMPLE_LIGHT_NONE.fetch_add(1, Ordering::Relaxed); return; }; let light = &self.lights[sampled_light.light.0 as usize]; let Some(ls) = light.sample_li(&light_ctx, rs.direct.u, &lambda, true) else { + DIAG_SAMPLE_LI_NONE.fetch_add(1, Ordering::Relaxed); return; }; - if ls.l.is_black() || ls.pdf <= 0.0 { + if ls.l.is_black() { + DIAG_LS_L_BLACK.fetch_add(1, Ordering::Relaxed); + return; + } + if ls.pdf <= 0.0 { + DIAG_LS_PDF_ZERO.fetch_add(1, Ordering::Relaxed); return; } let wi = ls.wi; let Some(f) = bsdf.f(wo, wi, TransportMode::Radiance) else { + DIAG_F_NONE.fetch_add(1, Ordering::Relaxed); return; }; if f.is_black() { + DIAG_F_BLACK.fetch_add(1, Ordering::Relaxed); return; } @@ -569,6 +650,19 @@ impl CpuWavefrontRenderer { &ls.p_light.n(), ); + DIAG_SHADOW_PUSH.fetch_add(1, Ordering::Relaxed); + if w.depth == 0 { + let n = DIAG_NEE_D0_PRINT.fetch_add(1, Ordering::Relaxed); + if n < 10 { + eprintln!( + "NEE_D0[{n}] pixel={:?} ls.l={:?} ls.pdf={:.6} f={:?} \ + beta={:?} light_pdf={:.6} bsdf_pdf={:.6} \ + r_u={:?} r_l={:?} l_d={:?}", + w.pixel_index, ls.l, ls.pdf, f, beta, + light_pdf, bsdf_pdf, r_u, r_l, l_d + ); + } + } shadow_ray_queue.push(ShadowRayWorkItem { ray, t_max: 1.0 - SHADOW_EPSILON,