basic_intersect for DiskShape computed the plane hit with interval arithmetic where
pbrt uses a scalar (shapes.h: Float tShapeHit = (height - Float(oi.z)) / Float(di.z)). It produced NaN, and since every NaN comparison is false neither the t range test nor the subsequent dist2 > radius^2 test could reject it. The disk reported a hit on every ray tested against it, and the NaN reached dpdu/ dpdv, the shading normal, and the area-light MIS pdf. NaN is absorbing in the film, so one bad sample poisoned pixel RgbSum permantenly. Wavefront now works
This commit is contained in:
parent
1b1344c393
commit
5038ed8270
4 changed files with 34 additions and 10 deletions
4
.cargo/config.toml
Normal file
4
.cargo/config.toml
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
[build]
|
||||||
|
# Enable the host's AVX2/FMA. Measured ~12% on the CPU wavefront (13700H).
|
||||||
|
# NOTE: pins binaries to this CPU family. Use "x86-64-v3" if you ship them.
|
||||||
|
rustflags = ["-C", "target-cpu=native"]
|
||||||
|
|
@ -63,14 +63,22 @@ impl DiskShape {
|
||||||
if Float::from(di.z()) == 0. {
|
if Float::from(di.z()) == 0. {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
let t_shape_hit: Interval = (self.height - oi.z()) / di.z();
|
// pbrt computes the plane hit as a SCALAR:
|
||||||
if t_shape_hit.high <= 0. || t_shape_hit.low >= t_max {
|
// Float tShapeHit = (height - Float(oi.z)) / Float(di.z);
|
||||||
|
// if (tShapeHit <= 0 || tShapeHit >= tMax) return {};
|
||||||
|
// Interval arithmetic here produced NaN, and neither `high <= 0` nor
|
||||||
|
// `low >= t_max` rejects a NaN (every NaN comparison is false). The NaN then
|
||||||
|
// flowed into p_hit -- so the `dist2 > radius^2` test could not reject it either,
|
||||||
|
// and the disk swallowed every ray it was tested against -- and on into dpdu/dpdv
|
||||||
|
// and the shading normal, poisoning the area-light MIS pdf.
|
||||||
|
let t_shape_hit: Float = (self.height - Float::from(oi.z())) / Float::from(di.z());
|
||||||
|
if t_shape_hit <= 0. || t_shape_hit >= t_max {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
let oi_f = Point3f::from(oi);
|
let oi_f = Point3f::from(oi);
|
||||||
let di_f = Vector3f::from(di);
|
let di_f = Vector3f::from(di);
|
||||||
let t = Float::from(t_shape_hit);
|
let t = t_shape_hit;
|
||||||
let p_hit: Point3f = oi_f + di_f * t;
|
let p_hit: Point3f = oi_f + di_f * t;
|
||||||
|
|
||||||
let dist2 = square(p_hit.x()) + square(p_hit.y());
|
let dist2 = square(p_hit.x()) + square(p_hit.y());
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
use crate::wavefront::integrator::RAYON_MIN_LEN;
|
||||||
use crate::globals::get_options;
|
use crate::globals::get_options;
|
||||||
use log::debug;
|
use log::debug;
|
||||||
use std::sync::atomic::{AtomicU32, Ordering};
|
use std::sync::atomic::{AtomicU32, Ordering};
|
||||||
|
|
@ -51,7 +52,7 @@ impl WavefrontAggregate for CpuAggregate {
|
||||||
let n_rays = ray_q.size().min(max_rays as u32);
|
let n_rays = ray_q.size().min(max_rays as u32);
|
||||||
|
|
||||||
// Intersect ray with the scene and enqueue resulting work
|
// Intersect ray with the scene and enqueue resulting work
|
||||||
(0..n_rays as usize).into_par_iter().for_each(|i| {
|
(0..n_rays as usize).into_par_iter().with_min_len(RAYON_MIN_LEN).for_each(|i| {
|
||||||
let r = unsafe { ray_q.get(i) };
|
let r = unsafe { ray_q.get(i) };
|
||||||
|
|
||||||
let Some(si) = self.aggregate.intersect(&r.ray, None) else {
|
let Some(si) = self.aggregate.intersect(&r.ray, None) else {
|
||||||
|
|
@ -172,7 +173,7 @@ impl WavefrontAggregate for CpuAggregate {
|
||||||
) {
|
) {
|
||||||
let n_rays = shadow_ray_q.size().min(max_rays as u32);
|
let n_rays = shadow_ray_q.size().min(max_rays as u32);
|
||||||
|
|
||||||
(0..n_rays as usize).into_par_iter().for_each(|i| {
|
(0..n_rays as usize).into_par_iter().with_min_len(RAYON_MIN_LEN).for_each(|i| {
|
||||||
let work = unsafe { shadow_ray_q.get(i) };
|
let work = unsafe { shadow_ray_q.get(i) };
|
||||||
|
|
||||||
let ray = Ray::new(work.ray.o, work.ray.d, Some(work.ray.time), Ptr::null());
|
let ray = Ray::new(work.ray.o, work.ray.d, Some(work.ray.time), Ptr::null());
|
||||||
|
|
|
||||||
|
|
@ -37,6 +37,10 @@ use std::ops::{Deref, DerefMut};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::sync::atomic::{AtomicU32, Ordering};
|
use std::sync::atomic::{AtomicU32, Ordering};
|
||||||
|
|
||||||
|
/// Minimum rayon chunk. The per-item bodies are short enough that splitting all the way
|
||||||
|
/// down costs more in work-stealing than it recovers in balance (~3x on this scene).
|
||||||
|
pub(crate) const RAYON_MIN_LEN: usize = 64;
|
||||||
|
|
||||||
static DIAG_EVAL_ENTER: AtomicU32 = AtomicU32::new(0);
|
static DIAG_EVAL_ENTER: AtomicU32 = AtomicU32::new(0);
|
||||||
static DIAG_BSDF_EMPTY: AtomicU32 = AtomicU32::new(0);
|
static DIAG_BSDF_EMPTY: AtomicU32 = AtomicU32::new(0);
|
||||||
static DIAG_NON_SPECULAR_SKIP: AtomicU32 = AtomicU32::new(0);
|
static DIAG_NON_SPECULAR_SKIP: AtomicU32 = AtomicU32::new(0);
|
||||||
|
|
@ -90,8 +94,13 @@ where
|
||||||
let filter = Ptr::from(&film.base().filter);
|
let filter = Ptr::from(&film.base().filter);
|
||||||
let light_sampler = create_light_sampler("power", &lights, arena);
|
let light_sampler = create_light_sampler("power", &lights, arena);
|
||||||
let res_x = pixel_bounds.diagonal().x() as u32;
|
let res_x = pixel_bounds.diagonal().x() as u32;
|
||||||
|
// pbrt (wavefront/integrator.cpp:231) rebalances so the last pass isn't mostly
|
||||||
|
// empty; without it every queue is oversized.
|
||||||
|
let res_y = pixel_bounds.diagonal().y() as u32;
|
||||||
let max_samples = 1024u32 * 1024;
|
let max_samples = 1024u32 * 1024;
|
||||||
let scanlines_per_pass = (max_samples / res_x).max(1);
|
let mut scanlines_per_pass = (max_samples / res_x).max(1);
|
||||||
|
let n_passes = res_y.div_ceil(scanlines_per_pass).max(1);
|
||||||
|
scanlines_per_pass = res_y.div_ceil(n_passes).max(1);
|
||||||
let max_queue_size = res_x * scanlines_per_pass;
|
let max_queue_size = res_x * scanlines_per_pass;
|
||||||
|
|
||||||
let mut infinite_lights: GVec<LightIdx> = gvec();
|
let mut infinite_lights: GVec<LightIdx> = gvec();
|
||||||
|
|
@ -323,6 +332,7 @@ impl CpuWavefrontRenderer {
|
||||||
// and every later kernel addresses state by this same absolute index.
|
// and every later kernel addresses state by this same absolute index.
|
||||||
(0..self.max_queue_size as usize)
|
(0..self.max_queue_size as usize)
|
||||||
.into_par_iter()
|
.into_par_iter()
|
||||||
|
.with_min_len(RAYON_MIN_LEN)
|
||||||
.for_each(|pixel_index| {
|
.for_each(|pixel_index| {
|
||||||
let p_pixel = Point2i::new(
|
let p_pixel = Point2i::new(
|
||||||
pixel_bounds.p_min.x() + (pixel_index as i32 % x_resolution),
|
pixel_bounds.p_min.x() + (pixel_index as i32 % x_resolution),
|
||||||
|
|
@ -386,7 +396,7 @@ impl CpuWavefrontRenderer {
|
||||||
let pixel_sample_state = &self.pixel_sample_state;
|
let pixel_sample_state = &self.pixel_sample_state;
|
||||||
let escaped_ray_queue = &self.escaped_ray_queue;
|
let escaped_ray_queue = &self.escaped_ray_queue;
|
||||||
|
|
||||||
(0..n as usize).into_par_iter().for_each(|i| {
|
(0..n as usize).into_par_iter().with_min_len(RAYON_MIN_LEN).for_each(|i| {
|
||||||
let w = unsafe { escaped_ray_queue.storage.get(i) };
|
let w = unsafe { escaped_ray_queue.storage.get(i) };
|
||||||
|
|
||||||
let mut l_contrib = SampledSpectrum::new(0.0);
|
let mut l_contrib = SampledSpectrum::new(0.0);
|
||||||
|
|
@ -425,7 +435,7 @@ impl CpuWavefrontRenderer {
|
||||||
let pixel_sample_state = &self.pixel_sample_state;
|
let pixel_sample_state = &self.pixel_sample_state;
|
||||||
let hit_area_light_queue = &self.hit_area_light_queue;
|
let hit_area_light_queue = &self.hit_area_light_queue;
|
||||||
|
|
||||||
(0..n as usize).into_par_iter().for_each(|i| {
|
(0..n as usize).into_par_iter().with_min_len(RAYON_MIN_LEN).for_each(|i| {
|
||||||
let w = unsafe { hit_area_light_queue.get(i) };
|
let w = unsafe { hit_area_light_queue.get(i) };
|
||||||
if w.area_light.is_none() {
|
if w.area_light.is_none() {
|
||||||
return;
|
return;
|
||||||
|
|
@ -483,7 +493,7 @@ impl CpuWavefrontRenderer {
|
||||||
let next_ray_queue = &self.ray_queues[next];
|
let next_ray_queue = &self.ray_queues[next];
|
||||||
let regularize = self.regularize;
|
let regularize = self.regularize;
|
||||||
|
|
||||||
(0..n as usize).into_par_iter().for_each(|i| {
|
(0..n as usize).into_par_iter().with_min_len(RAYON_MIN_LEN).for_each(|i| {
|
||||||
let w = unsafe { queue.storage.get(i) };
|
let w = unsafe { queue.storage.get(i) };
|
||||||
// Fires on every material-queue pass (reset each depth/batch/sample), so it
|
// Fires on every material-queue pass (reset each depth/batch/sample), so it
|
||||||
// ran continuously and dominated render time. Gated behind cpu_debug.
|
// ran continuously and dominated render time. Gated behind cpu_debug.
|
||||||
|
|
@ -726,6 +736,7 @@ impl CpuWavefrontRenderer {
|
||||||
fn update_film(&self, _y0: i32, _y1: i32, pixel_bounds: &Bounds2i) {
|
fn update_film(&self, _y0: i32, _y1: i32, pixel_bounds: &Bounds2i) {
|
||||||
(0..self.max_queue_size as usize)
|
(0..self.max_queue_size as usize)
|
||||||
.into_par_iter()
|
.into_par_iter()
|
||||||
|
.with_min_len(RAYON_MIN_LEN)
|
||||||
.for_each(|pixel_index| {
|
.for_each(|pixel_index| {
|
||||||
let p_pixel = self.pixel_sample_state.pixel.get(pixel_index);
|
let p_pixel = self.pixel_sample_state.pixel.get(pixel_index);
|
||||||
if !pixel_bounds.contains_exclusive(p_pixel) {
|
if !pixel_bounds.contains_exclusive(p_pixel) {
|
||||||
|
|
@ -749,7 +760,7 @@ impl CpuWavefrontRenderer {
|
||||||
let pixel_sample_state = &self.pixel_sample_state;
|
let pixel_sample_state = &self.pixel_sample_state;
|
||||||
let sampler_proto = &self.sampler;
|
let sampler_proto = &self.sampler;
|
||||||
|
|
||||||
(0..n as usize).into_par_iter().for_each(|i| {
|
(0..n as usize).into_par_iter().with_min_len(RAYON_MIN_LEN).for_each(|i| {
|
||||||
let w = unsafe { ray_queue.storage.get(i) };
|
let w = unsafe { ray_queue.storage.get(i) };
|
||||||
let dimension = 6 + 7 * w.depth;
|
let dimension = 6 + 7 * w.depth;
|
||||||
let pi = w.pixel_index as usize;
|
let pi = w.pixel_index as usize;
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue