From 5dd8eb919bf5eb3b06083cff26d5a8314627d8f2 Mon Sep 17 00:00:00 2001 From: Nathan Vegdahl Date: Tue, 25 Jun 2019 17:31:51 +0900 Subject: [PATCH] Changed ray batch data access to be through methods. This is (potentially) just temporary. It's to make it a bit easier to play with data layout to see how that affects performance. --- src/accel/bvh4.rs | 18 +++--- src/light/rectangle_light.rs | 12 ++-- src/light/sphere_light.rs | 22 ++++---- src/ray.rs | 105 +++++++++++++++++++++++++---------- src/renderer.rs | 4 +- src/surface/triangle_mesh.rs | 12 ++-- src/tracer.rs | 14 ++--- 7 files changed, 117 insertions(+), 70 deletions(-) diff --git a/src/accel/bvh4.rs b/src/accel/bvh4.rs index 86b2389..b21a5a2 100644 --- a/src/accel/bvh4.rs +++ b/src/accel/bvh4.rs @@ -95,7 +95,7 @@ impl<'a> BVH4<'a> { let mut node_tests: u64 = 0; let traversal_table = - &TRAVERSAL_TABLE[ray_code(rays.dir_inv_accel[ray_stack.next_task_ray_idx(0)])]; + &TRAVERSAL_TABLE[ray_code(rays.dir_inv_local(ray_stack.next_task_ray_idx(0)))]; // +2 of max depth for root and last child let mut node_stack = [self.root.unwrap(); (BVH_MAX_DEPTH * 3) + 2]; @@ -117,10 +117,10 @@ impl<'a> BVH4<'a> { let mut hit_count = 0; ray_stack.pop_do_next_task(children.len(), |ray_idx| { let hit = (!rays.is_done(ray_idx)) - && lerp_slice(bounds, rays.time[ray_idx]).intersect_ray( - rays.orig_accel[ray_idx], - rays.dir_inv_accel[ray_idx], - rays.max_t[ray_idx], + && lerp_slice(bounds, rays.time(ray_idx)).intersect_ray( + rays.orig_local(ray_idx), + rays.dir_inv_local(ray_idx), + rays.max_t(ray_idx), ); if hit { @@ -194,10 +194,10 @@ impl<'a> BVH4<'a> { ray_stack.pop_do_next_task(object_count, |ray_idx| { let hit = (!rays.is_done(ray_idx)) - && lerp_slice(bounds, rays.time[ray_idx]).intersect_ray( - rays.orig_accel[ray_idx], - rays.dir_inv_accel[ray_idx], - rays.max_t[ray_idx], + && lerp_slice(bounds, rays.time(ray_idx)).intersect_ray( + rays.orig_local(ray_idx), + rays.dir_inv_local(ray_idx), + rays.max_t(ray_idx), ); if hit { hit_count += 1; diff --git a/src/light/rectangle_light.rs b/src/light/rectangle_light.rs index 4711c36..f7af205 100644 --- a/src/light/rectangle_light.rs +++ b/src/light/rectangle_light.rs @@ -266,10 +266,10 @@ impl<'a> Surface for RectangleLight<'a> { let _ = shader; // Silence 'unused' warning ray_stack.pop_do_next_task(0, |ray_idx| { - let time = rays.time[ray_idx]; - let orig = rays.orig_world[ray_idx]; - let dir = rays.dir_world[ray_idx]; - let max_t = rays.max_t[ray_idx]; + let time = rays.time(ray_idx); + let orig = rays.orig(ray_idx); + let dir = rays.dir(ray_idx); + let max_t = rays.max_t(ray_idx); // Calculate time interpolated values let dim = lerp_slice(self.dimensions, time); @@ -307,7 +307,7 @@ impl<'a> Surface for RectangleLight<'a> { orig, dir, pos, - rays.wavelength[ray_idx], + rays.wavelength(ray_idx), time, ), }; @@ -325,7 +325,7 @@ impl<'a> Surface for RectangleLight<'a> { }; // Set ray's max t - rays.max_t[ray_idx] = t; + rays.set_max_t(ray_idx, t); } break; diff --git a/src/light/sphere_light.rs b/src/light/sphere_light.rs index 944baa8..ace32f7 100644 --- a/src/light/sphere_light.rs +++ b/src/light/sphere_light.rs @@ -215,7 +215,7 @@ impl<'a> Surface for SphereLight<'a> { let _ = shader; // Silence 'unused' warning ray_stack.pop_do_next_task(0, |ray_idx| { - let time = rays.time[ray_idx]; + let time = rays.time(ray_idx); // Get the transform space let xform = lerp_slice(space, time); @@ -224,8 +224,8 @@ impl<'a> Surface for SphereLight<'a> { let radius = lerp_slice(self.radii, time); // Radius of the sphere // Get the ray origin and direction in local space - let orig = rays.orig_accel[ray_idx].into_vector(); - let dir = rays.dir_world[ray_idx] * xform; + let orig = rays.orig(ray_idx).into_vector(); + let dir = rays.dir(ray_idx) * xform; // Code adapted to Rust from https://github.com/Tecla/Rayito // Ray-sphere intersection can result in either zero, one or two points @@ -257,7 +257,7 @@ impl<'a> Surface for SphereLight<'a> { // Get our final parametric values let mut t0 = q / a; - let mut t1 = if q != 0.0 { c / q } else { rays.max_t[ray_idx] }; + let mut t1 = if q != 0.0 { c / q } else { rays.max_t(ray_idx) }; // Swap them so they are ordered right if t0 > t1 { @@ -266,14 +266,14 @@ impl<'a> Surface for SphereLight<'a> { } // Check our intersection for validity against this ray's extents - if t0 > rays.max_t[ray_idx] || t1 <= 0.0 { + if t0 > rays.max_t(ray_idx) || t1 <= 0.0 { // Didn't hit because sphere is entirely outside of ray's extents return ([0, 0, 0, 0, 0, 0, 0, 0], 0); } let t = if t0 > 0.0 { t0 - } else if t1 <= rays.max_t[ray_idx] { + } else if t1 <= rays.max_t(ray_idx) { t1 } else { // Didn't hit because ray is entirely within the sphere, and @@ -300,7 +300,7 @@ impl<'a> Surface for SphereLight<'a> { let normal = unit_pos.into_normal() * inv_xform; let intersection_data = SurfaceIntersectionData { - incoming: rays.dir_world[ray_idx], + incoming: rays.dir(ray_idx), t: t, pos: pos, pos_err: pos_err, @@ -309,11 +309,11 @@ impl<'a> Surface for SphereLight<'a> { local_space: xform, sample_pdf: self.sample_pdf( &xform, - rays.orig_world[ray_idx], - rays.dir_world[ray_idx], + rays.orig(ray_idx), + rays.dir(ray_idx), 0.0, 0.0, - rays.wavelength[ray_idx], + rays.wavelength(ray_idx), time, ), }; @@ -332,7 +332,7 @@ impl<'a> Surface for SphereLight<'a> { }; // Set ray's max t - rays.max_t[ray_idx] = t; + rays.set_max_t(ray_idx, t); } ([0, 0, 0, 0, 0, 0, 0, 0], 0) diff --git a/src/ray.rs b/src/ray.rs index 585b0cc..214b0eb 100644 --- a/src/ray.rs +++ b/src/ray.rs @@ -23,14 +23,14 @@ pub struct Ray { /// A batch of rays, stored in SoA layout. #[derive(Debug)] pub struct RayBatch { - pub orig_world: Vec, - pub dir_world: Vec, - pub orig_accel: Vec, - pub dir_inv_accel: Vec, - pub max_t: Vec, - pub time: Vec, - pub wavelength: Vec, - pub flags: Vec, + orig_world: Vec, + dir_world: Vec, + orig_accel: Vec, + dir_inv_accel: Vec, + max_t: Vec, + time: Vec, + wavelength: Vec, + flags: Vec, } impl RayBatch { @@ -135,37 +135,84 @@ impl RayBatch { self.orig_world.len() } - /// Returns whether the given ray (at index `idx`) is an occlusion ray. - pub fn is_occlusion(&self, idx: usize) -> bool { - (self.flags[idx] & OCCLUSION_FLAG) != 0 - } - - /// Returns whether the given ray (at index `idx`) has finished traversal. - pub fn is_done(&self, idx: usize) -> bool { - (self.flags[idx] & DONE_FLAG) != 0 - } - - /// Marks the given ray (at index `idx`) as an occlusion ray. - pub fn mark_occlusion(&mut self, idx: usize) { - self.flags[idx] |= OCCLUSION_FLAG - } - - /// Marks the given ray (at index `idx`) as having finished traversal. - pub fn mark_done(&mut self, idx: usize) { - self.flags[idx] |= DONE_FLAG - } - /// Updates the accel data of the given ray (at index `idx`) with the /// given world-to-local-space transform matrix. /// /// This should be called when entering (and exiting) traversal of a /// new transform space. - pub fn update_accel(&mut self, idx: usize, xform: &Matrix4x4) { + pub fn update_local(&mut self, idx: usize, xform: &Matrix4x4) { self.orig_accel[idx] = self.orig_world[idx] * *xform; self.dir_inv_accel[idx] = Vector { co: Float4::splat(1.0) / (self.dir_world[idx] * *xform).co, }; } + + //========================================================== + // Data access + + #[inline(always)] + pub fn orig(&self, idx: usize) -> Point { + self.orig_world[idx] + } + + #[inline(always)] + pub fn dir(&self, idx: usize) -> Vector { + self.dir_world[idx] + } + + #[inline(always)] + pub fn orig_local(&self, idx: usize) -> Point { + self.orig_accel[idx] + } + + #[inline(always)] + pub fn dir_inv_local(&self, idx: usize) -> Vector { + self.dir_inv_accel[idx] + } + + #[inline(always)] + pub fn time(&self, idx: usize) -> f32 { + self.time[idx] + } + + #[inline(always)] + pub fn max_t(&self, idx: usize) -> f32 { + self.max_t[idx] + } + + #[inline(always)] + pub fn set_max_t(&mut self, idx: usize, new_max_t: f32) { + self.max_t[idx] = new_max_t; + } + + #[inline(always)] + pub fn wavelength(&self, idx: usize) -> f32 { + self.wavelength[idx] + } + + /// Returns whether the given ray (at index `idx`) is an occlusion ray. + #[inline(always)] + pub fn is_occlusion(&self, idx: usize) -> bool { + (self.flags[idx] & OCCLUSION_FLAG) != 0 + } + + /// Returns whether the given ray (at index `idx`) has finished traversal. + #[inline(always)] + pub fn is_done(&self, idx: usize) -> bool { + (self.flags[idx] & DONE_FLAG) != 0 + } + + /// Marks the given ray (at index `idx`) as an occlusion ray. + #[inline(always)] + pub fn mark_occlusion(&mut self, idx: usize) { + self.flags[idx] |= OCCLUSION_FLAG + } + + /// Marks the given ray (at index `idx`) as having finished traversal. + #[inline(always)] + pub fn mark_done(&mut self, idx: usize) { + self.flags[idx] |= DONE_FLAG + } } /// A structure used for tracking traversal of a ray batch through a scene. diff --git a/src/renderer.rs b/src/renderer.rs index 6f3fe80..2c3077d 100644 --- a/src/renderer.rs +++ b/src/renderer.rs @@ -503,7 +503,7 @@ impl LightPath { // Distant light SceneLightSample::Distant { direction, .. } => { let (attenuation, closure_pdf) = closure.evaluate( - rays.dir_world[ray_idx], + rays.dir(ray_idx), direction, idata.nor, idata.nor_g, @@ -533,7 +533,7 @@ impl LightPath { SceneLightSample::Surface { sample_geo, .. } => { let dir = sample_geo.0 - idata.pos; let (attenuation, closure_pdf) = closure.evaluate( - rays.dir_world[ray_idx], + rays.dir(ray_idx), dir, idata.nor, idata.nor_g, diff --git a/src/surface/triangle_mesh.rs b/src/surface/triangle_mesh.rs index 38f3ebf..a1f8cb6 100644 --- a/src/surface/triangle_mesh.rs +++ b/src/surface/triangle_mesh.rs @@ -159,7 +159,7 @@ impl<'a> Surface for TriangleMesh<'a> { // Test each ray against the current triangle. ray_stack.pop_do_next_task(0, |ray_idx| { let ray_idx = ray_idx as usize; - let ray_time = rays.time[ray_idx]; + let ray_time = rays.time(ray_idx); // Get triangle if necessary if !is_cached { @@ -215,9 +215,9 @@ impl<'a> Surface for TriangleMesh<'a> { // Test ray against triangle if let Some((t, b0, b1, b2)) = triangle::intersect_ray( - rays.orig_world[ray_idx], - rays.dir_world[ray_idx], - rays.max_t[ray_idx], + rays.orig(ray_idx), + rays.dir(ray_idx), + rays.max_t(ray_idx), tri, ) { if rays.is_occlusion(ray_idx) { @@ -257,7 +257,7 @@ impl<'a> Surface for TriangleMesh<'a> { }; let intersection_data = SurfaceIntersectionData { - incoming: rays.dir_world[ray_idx], + incoming: rays.dir(ray_idx), t: t, pos: pos, pos_err: pos_err, @@ -272,7 +272,7 @@ impl<'a> Surface for TriangleMesh<'a> { intersection_data: intersection_data, closure: shader.shade(&intersection_data, ray_time), }; - rays.max_t[ray_idx] = t; + rays.set_max_t(ray_idx, t); } } diff --git a/src/tracer.rs b/src/tracer.rs index 3fba96e..d11d0bb 100644 --- a/src/tracer.rs +++ b/src/tracer.rs @@ -58,14 +58,14 @@ impl<'a> TracerInner<'a> { { let ident = Matrix4x4::new(); for i in 0..rays.len() { - rays.update_accel(i, &ident); + rays.update_local(i, &ident); } } // Divide the rays into 8 different lanes by direction. ray_stack.ensure_lane_count(8); for i in 0..rays.len() { - ray_stack.push_ray_index(i, ray_code(rays.dir_world[i])); + ray_stack.push_ray_index(i, ray_code(rays.dir(i))); } ray_stack.push_lanes_to_tasks(&[0, 1, 2, 3, 4, 5, 6, 7]); @@ -97,8 +97,8 @@ impl<'a> TracerInner<'a> { // TODO: re-divide rays based on direction (maybe?). let xforms = self.xform_stack.top(); ray_stack.pop_do_next_task(2, |ray_idx| { - let t = rays.time[ray_idx]; - rays.update_accel(ray_idx, &lerp_slice(xforms, t)); + let t = rays.time(ray_idx); + rays.update_local(ray_idx, &lerp_slice(xforms, t)); ([0, 1, 2, 3, 4, 5, 6, 7], 2) }); ray_stack.push_lanes_to_tasks(&[0, 1]); @@ -130,14 +130,14 @@ impl<'a> TracerInner<'a> { let xforms = self.xform_stack.top(); if !xforms.is_empty() { ray_stack.pop_do_next_task(0, |ray_idx| { - let t = rays.time[ray_idx]; - rays.update_accel(ray_idx, &lerp_slice(xforms, t)); + let t = rays.time(ray_idx); + rays.update_local(ray_idx, &lerp_slice(xforms, t)); ([0, 1, 2, 3, 4, 5, 6, 7], 0) }); } else { let ident = Matrix4x4::new(); ray_stack.pop_do_next_task(0, |ray_idx| { - rays.update_accel(ray_idx, &ident); + rays.update_local(ray_idx, &ident); ([0, 1, 2, 3, 4, 5, 6, 7], 0) }); }