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.
This commit is contained in:
Nathan Vegdahl 2019-06-25 17:31:51 +09:00
parent eef29c2b2f
commit 5dd8eb919b
7 changed files with 117 additions and 70 deletions

View File

@ -95,7 +95,7 @@ impl<'a> BVH4<'a> {
let mut node_tests: u64 = 0; let mut node_tests: u64 = 0;
let traversal_table = 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 // +2 of max depth for root and last child
let mut node_stack = [self.root.unwrap(); (BVH_MAX_DEPTH * 3) + 2]; 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; let mut hit_count = 0;
ray_stack.pop_do_next_task(children.len(), |ray_idx| { ray_stack.pop_do_next_task(children.len(), |ray_idx| {
let hit = (!rays.is_done(ray_idx)) let hit = (!rays.is_done(ray_idx))
&& lerp_slice(bounds, rays.time[ray_idx]).intersect_ray( && lerp_slice(bounds, rays.time(ray_idx)).intersect_ray(
rays.orig_accel[ray_idx], rays.orig_local(ray_idx),
rays.dir_inv_accel[ray_idx], rays.dir_inv_local(ray_idx),
rays.max_t[ray_idx], rays.max_t(ray_idx),
); );
if hit { if hit {
@ -194,10 +194,10 @@ impl<'a> BVH4<'a> {
ray_stack.pop_do_next_task(object_count, |ray_idx| { ray_stack.pop_do_next_task(object_count, |ray_idx| {
let hit = (!rays.is_done(ray_idx)) let hit = (!rays.is_done(ray_idx))
&& lerp_slice(bounds, rays.time[ray_idx]).intersect_ray( && lerp_slice(bounds, rays.time(ray_idx)).intersect_ray(
rays.orig_accel[ray_idx], rays.orig_local(ray_idx),
rays.dir_inv_accel[ray_idx], rays.dir_inv_local(ray_idx),
rays.max_t[ray_idx], rays.max_t(ray_idx),
); );
if hit { if hit {
hit_count += 1; hit_count += 1;

View File

@ -266,10 +266,10 @@ impl<'a> Surface for RectangleLight<'a> {
let _ = shader; // Silence 'unused' warning let _ = shader; // Silence 'unused' warning
ray_stack.pop_do_next_task(0, |ray_idx| { ray_stack.pop_do_next_task(0, |ray_idx| {
let time = rays.time[ray_idx]; let time = rays.time(ray_idx);
let orig = rays.orig_world[ray_idx]; let orig = rays.orig(ray_idx);
let dir = rays.dir_world[ray_idx]; let dir = rays.dir(ray_idx);
let max_t = rays.max_t[ray_idx]; let max_t = rays.max_t(ray_idx);
// Calculate time interpolated values // Calculate time interpolated values
let dim = lerp_slice(self.dimensions, time); let dim = lerp_slice(self.dimensions, time);
@ -307,7 +307,7 @@ impl<'a> Surface for RectangleLight<'a> {
orig, orig,
dir, dir,
pos, pos,
rays.wavelength[ray_idx], rays.wavelength(ray_idx),
time, time,
), ),
}; };
@ -325,7 +325,7 @@ impl<'a> Surface for RectangleLight<'a> {
}; };
// Set ray's max t // Set ray's max t
rays.max_t[ray_idx] = t; rays.set_max_t(ray_idx, t);
} }
break; break;

View File

@ -215,7 +215,7 @@ impl<'a> Surface for SphereLight<'a> {
let _ = shader; // Silence 'unused' warning let _ = shader; // Silence 'unused' warning
ray_stack.pop_do_next_task(0, |ray_idx| { 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 // Get the transform space
let xform = lerp_slice(space, time); 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 let radius = lerp_slice(self.radii, time); // Radius of the sphere
// Get the ray origin and direction in local space // Get the ray origin and direction in local space
let orig = rays.orig_accel[ray_idx].into_vector(); let orig = rays.orig(ray_idx).into_vector();
let dir = rays.dir_world[ray_idx] * xform; let dir = rays.dir(ray_idx) * xform;
// Code adapted to Rust from https://github.com/Tecla/Rayito // Code adapted to Rust from https://github.com/Tecla/Rayito
// Ray-sphere intersection can result in either zero, one or two points // 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 // Get our final parametric values
let mut t0 = q / a; 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 // Swap them so they are ordered right
if t0 > t1 { if t0 > t1 {
@ -266,14 +266,14 @@ impl<'a> Surface for SphereLight<'a> {
} }
// Check our intersection for validity against this ray's extents // 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 // Didn't hit because sphere is entirely outside of ray's extents
return ([0, 0, 0, 0, 0, 0, 0, 0], 0); return ([0, 0, 0, 0, 0, 0, 0, 0], 0);
} }
let t = if t0 > 0.0 { let t = if t0 > 0.0 {
t0 t0
} else if t1 <= rays.max_t[ray_idx] { } else if t1 <= rays.max_t(ray_idx) {
t1 t1
} else { } else {
// Didn't hit because ray is entirely within the sphere, and // 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 normal = unit_pos.into_normal() * inv_xform;
let intersection_data = SurfaceIntersectionData { let intersection_data = SurfaceIntersectionData {
incoming: rays.dir_world[ray_idx], incoming: rays.dir(ray_idx),
t: t, t: t,
pos: pos, pos: pos,
pos_err: pos_err, pos_err: pos_err,
@ -309,11 +309,11 @@ impl<'a> Surface for SphereLight<'a> {
local_space: xform, local_space: xform,
sample_pdf: self.sample_pdf( sample_pdf: self.sample_pdf(
&xform, &xform,
rays.orig_world[ray_idx], rays.orig(ray_idx),
rays.dir_world[ray_idx], rays.dir(ray_idx),
0.0, 0.0,
0.0, 0.0,
rays.wavelength[ray_idx], rays.wavelength(ray_idx),
time, time,
), ),
}; };
@ -332,7 +332,7 @@ impl<'a> Surface for SphereLight<'a> {
}; };
// Set ray's max t // 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) ([0, 0, 0, 0, 0, 0, 0, 0], 0)

View File

@ -23,14 +23,14 @@ pub struct Ray {
/// A batch of rays, stored in SoA layout. /// A batch of rays, stored in SoA layout.
#[derive(Debug)] #[derive(Debug)]
pub struct RayBatch { pub struct RayBatch {
pub orig_world: Vec<Point>, orig_world: Vec<Point>,
pub dir_world: Vec<Vector>, dir_world: Vec<Vector>,
pub orig_accel: Vec<Point>, orig_accel: Vec<Point>,
pub dir_inv_accel: Vec<Vector>, dir_inv_accel: Vec<Vector>,
pub max_t: Vec<f32>, max_t: Vec<f32>,
pub time: Vec<f32>, time: Vec<f32>,
pub wavelength: Vec<f32>, wavelength: Vec<f32>,
pub flags: Vec<FlagType>, flags: Vec<FlagType>,
} }
impl RayBatch { impl RayBatch {
@ -135,37 +135,84 @@ impl RayBatch {
self.orig_world.len() 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 /// Updates the accel data of the given ray (at index `idx`) with the
/// given world-to-local-space transform matrix. /// given world-to-local-space transform matrix.
/// ///
/// This should be called when entering (and exiting) traversal of a /// This should be called when entering (and exiting) traversal of a
/// new transform space. /// 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.orig_accel[idx] = self.orig_world[idx] * *xform;
self.dir_inv_accel[idx] = Vector { self.dir_inv_accel[idx] = Vector {
co: Float4::splat(1.0) / (self.dir_world[idx] * *xform).co, 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. /// A structure used for tracking traversal of a ray batch through a scene.

View File

@ -503,7 +503,7 @@ impl LightPath {
// Distant light // Distant light
SceneLightSample::Distant { direction, .. } => { SceneLightSample::Distant { direction, .. } => {
let (attenuation, closure_pdf) = closure.evaluate( let (attenuation, closure_pdf) = closure.evaluate(
rays.dir_world[ray_idx], rays.dir(ray_idx),
direction, direction,
idata.nor, idata.nor,
idata.nor_g, idata.nor_g,
@ -533,7 +533,7 @@ impl LightPath {
SceneLightSample::Surface { sample_geo, .. } => { SceneLightSample::Surface { sample_geo, .. } => {
let dir = sample_geo.0 - idata.pos; let dir = sample_geo.0 - idata.pos;
let (attenuation, closure_pdf) = closure.evaluate( let (attenuation, closure_pdf) = closure.evaluate(
rays.dir_world[ray_idx], rays.dir(ray_idx),
dir, dir,
idata.nor, idata.nor,
idata.nor_g, idata.nor_g,

View File

@ -159,7 +159,7 @@ impl<'a> Surface for TriangleMesh<'a> {
// Test each ray against the current triangle. // Test each ray against the current triangle.
ray_stack.pop_do_next_task(0, |ray_idx| { ray_stack.pop_do_next_task(0, |ray_idx| {
let ray_idx = ray_idx as usize; 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 // Get triangle if necessary
if !is_cached { if !is_cached {
@ -215,9 +215,9 @@ impl<'a> Surface for TriangleMesh<'a> {
// Test ray against triangle // Test ray against triangle
if let Some((t, b0, b1, b2)) = triangle::intersect_ray( if let Some((t, b0, b1, b2)) = triangle::intersect_ray(
rays.orig_world[ray_idx], rays.orig(ray_idx),
rays.dir_world[ray_idx], rays.dir(ray_idx),
rays.max_t[ray_idx], rays.max_t(ray_idx),
tri, tri,
) { ) {
if rays.is_occlusion(ray_idx) { if rays.is_occlusion(ray_idx) {
@ -257,7 +257,7 @@ impl<'a> Surface for TriangleMesh<'a> {
}; };
let intersection_data = SurfaceIntersectionData { let intersection_data = SurfaceIntersectionData {
incoming: rays.dir_world[ray_idx], incoming: rays.dir(ray_idx),
t: t, t: t,
pos: pos, pos: pos,
pos_err: pos_err, pos_err: pos_err,
@ -272,7 +272,7 @@ impl<'a> Surface for TriangleMesh<'a> {
intersection_data: intersection_data, intersection_data: intersection_data,
closure: shader.shade(&intersection_data, ray_time), closure: shader.shade(&intersection_data, ray_time),
}; };
rays.max_t[ray_idx] = t; rays.set_max_t(ray_idx, t);
} }
} }

View File

@ -58,14 +58,14 @@ impl<'a> TracerInner<'a> {
{ {
let ident = Matrix4x4::new(); let ident = Matrix4x4::new();
for i in 0..rays.len() { 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. // Divide the rays into 8 different lanes by direction.
ray_stack.ensure_lane_count(8); ray_stack.ensure_lane_count(8);
for i in 0..rays.len() { 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]); 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?). // TODO: re-divide rays based on direction (maybe?).
let xforms = self.xform_stack.top(); let xforms = self.xform_stack.top();
ray_stack.pop_do_next_task(2, |ray_idx| { ray_stack.pop_do_next_task(2, |ray_idx| {
let t = rays.time[ray_idx]; let t = rays.time(ray_idx);
rays.update_accel(ray_idx, &lerp_slice(xforms, t)); rays.update_local(ray_idx, &lerp_slice(xforms, t));
([0, 1, 2, 3, 4, 5, 6, 7], 2) ([0, 1, 2, 3, 4, 5, 6, 7], 2)
}); });
ray_stack.push_lanes_to_tasks(&[0, 1]); ray_stack.push_lanes_to_tasks(&[0, 1]);
@ -130,14 +130,14 @@ impl<'a> TracerInner<'a> {
let xforms = self.xform_stack.top(); let xforms = self.xform_stack.top();
if !xforms.is_empty() { if !xforms.is_empty() {
ray_stack.pop_do_next_task(0, |ray_idx| { ray_stack.pop_do_next_task(0, |ray_idx| {
let t = rays.time[ray_idx]; let t = rays.time(ray_idx);
rays.update_accel(ray_idx, &lerp_slice(xforms, t)); rays.update_local(ray_idx, &lerp_slice(xforms, t));
([0, 1, 2, 3, 4, 5, 6, 7], 0) ([0, 1, 2, 3, 4, 5, 6, 7], 0)
}); });
} else { } else {
let ident = Matrix4x4::new(); let ident = Matrix4x4::new();
ray_stack.pop_do_next_task(0, |ray_idx| { 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) ([0, 1, 2, 3, 4, 5, 6, 7], 0)
}); });
} }