mirror of
https://github.com/bherbruck/solidcraft.git
synced 2026-11-03 02:25:55 +00:00
Raytracer: map faces to looks once (#10502)
* claim: Raytracer: map faces to looks once * Raytracer: map faces to looks once per body Preparing a body for the CPU raytracer walked the body's face look list one entry per work step for every triangle, so a body cost T x (F + 1) steps (each through the job host) instead of T. The face looks are now sorted once per body, keeping the first entry per face as before, and each triangle finds its look with one binary search. Pixels are unchanged; with 10 000 faces and 50 to 500 face looks, preparation drops by 40 to 88 %. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Markus Kaltenbrunner <kaltenbrunnermarkus@users.noreply.github.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
committed by
GitHub
parent
16f00d4cf9
commit
228b311a6f
@@ -471,8 +471,10 @@ pub struct ImageTask {
|
||||
looks: Vec<Look>,
|
||||
body: usize,
|
||||
triangle: usize,
|
||||
face_cursor: usize,
|
||||
chosen: Option<crate::FaceAppearance>,
|
||||
/// The current body's face looks as (face, index into `faces`), sorted by
|
||||
/// face with only the first entry per face, so each triangle is one lookup.
|
||||
face_looks: Vec<(u32, usize)>,
|
||||
face_looks_ready: bool,
|
||||
builder: Option<BvhBuilder>,
|
||||
bvh: Option<Bvh>,
|
||||
build_used: u64,
|
||||
@@ -624,8 +626,8 @@ impl ImageTask {
|
||||
looks,
|
||||
body: 0,
|
||||
triangle: 0,
|
||||
face_cursor: 0,
|
||||
chosen: None,
|
||||
face_looks: Vec::new(),
|
||||
face_looks_ready: false,
|
||||
builder: None,
|
||||
bvh: None,
|
||||
build_used: 0,
|
||||
@@ -682,8 +684,20 @@ impl ImageTask {
|
||||
let Some(indices) = body.mesh.triangles.get(self.triangle) else {
|
||||
self.body += 1;
|
||||
self.triangle = 0;
|
||||
self.face_looks_ready = false;
|
||||
return Ok(());
|
||||
};
|
||||
if !self.face_looks_ready {
|
||||
// Built once per body instead of scanning `faces` for every triangle.
|
||||
// The first entry for a face wins, as the scan this replaces did.
|
||||
self.work.charge(body.faces.len() as u64, &self.cancel).map_err(error)?;
|
||||
self.face_looks.clear();
|
||||
self.face_looks.try_reserve_exact(body.faces.len()).map_err(|_| error(TraceError::AllocationFailed))?;
|
||||
self.face_looks.extend(body.faces.iter().enumerate().map(|(index, look)| (look.face, index)));
|
||||
self.face_looks.sort_unstable();
|
||||
self.face_looks.dedup_by_key(|entry| entry.0);
|
||||
self.face_looks_ready = true;
|
||||
}
|
||||
let mut vertices = [Vec3::ZERO; 3];
|
||||
let mut normals = [Vec3::ZERO; 3];
|
||||
for ((p, n), i) in vertices.iter_mut().zip(normals.iter_mut()).zip(indices) {
|
||||
@@ -693,20 +707,14 @@ impl ImageTask {
|
||||
let face = body.mesh.tri_face.get(self.triangle).copied();
|
||||
let mut color = body.color;
|
||||
let mut surface = body.surface;
|
||||
if let Some(look) = body.faces.get(self.face_cursor) {
|
||||
self.face_cursor += 1;
|
||||
if Some(look.face) == face {
|
||||
self.chosen = Some(look.clone());
|
||||
self.face_cursor = body.faces.len();
|
||||
} else {
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
if let Some(look) = self.chosen.take() {
|
||||
let look = face
|
||||
.and_then(|face| self.face_looks.binary_search_by_key(&face, |entry| entry.0).ok())
|
||||
.and_then(|at| self.face_looks.get(at))
|
||||
.and_then(|entry| body.faces.get(entry.1));
|
||||
if let Some(look) = look {
|
||||
color = look.color;
|
||||
surface = look.surface;
|
||||
}
|
||||
self.face_cursor = 0;
|
||||
let surface = match surface {
|
||||
Some(s) => s,
|
||||
None => OpaqueSurface::new(Vec3::new(srgb8_to_linear(color[0]), srgb8_to_linear(color[1]), srgb8_to_linear(color[2])), 0.0, 0.7, 1.5)
|
||||
|
||||
@@ -351,6 +351,69 @@ fn triangle_task(cancel: CancelToken) -> ImageTask {
|
||||
ImageTask::new(scene, request, cancel, Default::default()).unwrap()
|
||||
}
|
||||
#[test]
|
||||
fn face_looks_cost_one_unit_per_triangle_and_the_first_entry_wins() {
|
||||
use std::sync::Arc;
|
||||
let (columns, rows) = (8u32, 4u32);
|
||||
let mut positions = Vec::new();
|
||||
for y in 0..=rows {
|
||||
for x in 0..=columns {
|
||||
positions.push(Vec3::new(f64::from(x) * 5.0 - 20.0, f64::from(y) * 5.0 - 10.0, 0.0));
|
||||
}
|
||||
}
|
||||
let (mut triangles, mut tri_face) = (Vec::new(), Vec::new());
|
||||
for y in 0..rows {
|
||||
for x in 0..columns {
|
||||
let a = y * (columns + 1) + x;
|
||||
triangles.extend([[a, a + 1, a + columns + 2], [a, a + columns + 2, a + columns + 1]]);
|
||||
tri_face.extend([y * columns + x; 2]);
|
||||
}
|
||||
}
|
||||
let count = positions.len();
|
||||
let mesh = solidcraft_geom::Mesh { positions, normals: vec![Vec3::Z; count], triangles, tri_face, ..Default::default() };
|
||||
let look = |face: u32, color: [u8; 3]| crate::FaceAppearance { face, color, opacity: 1.0, surface: None, transmission: None };
|
||||
let mut faces: Vec<_> = (0..columns * rows).step_by(2).map(|face| look(face, [face as u8 * 8, 100, 200])).collect();
|
||||
// Later duplicates never replace the first entry for a face.
|
||||
faces.extend([look(2, [1, 2, 3]), look(4, [4, 5, 6])]);
|
||||
let entries = faces.len() as u64;
|
||||
let bounds = mesh.bounds();
|
||||
let scene = ProductScene {
|
||||
meshes: vec![crate::ProductMesh {
|
||||
transmission: None,
|
||||
name: "face look fixture".into(),
|
||||
mesh: Arc::new(mesh),
|
||||
color: [220; 3],
|
||||
opacity: 1.0,
|
||||
surface: None,
|
||||
faces,
|
||||
}],
|
||||
bounds,
|
||||
};
|
||||
let mut request = request();
|
||||
request.camera.distance = 60.0;
|
||||
request.camera.set_view(crate::StandardView::Top);
|
||||
let mut task = ImageTask::new(scene, request, CancelToken::default(), Default::default()).unwrap();
|
||||
let before = task.work.used();
|
||||
for _ in 0..10_000 {
|
||||
if task.builder.is_some() || task.bvh.is_some() {
|
||||
break;
|
||||
}
|
||||
assert!(matches!(task.step(1).unwrap(), TaskPoll::Pending(_)));
|
||||
}
|
||||
assert!(task.builder.is_some() || task.bvh.is_some(), "preparation did not finish");
|
||||
let triangles = u64::from(2 * columns * rows);
|
||||
// One unit per triangle and per face entry, plus the end of the body and the
|
||||
// hand-over to the BVH builder; scanning the entries per triangle cost ~T*F.
|
||||
assert_eq!(task.work.used() - before, triangles + entries + 2);
|
||||
let surface =
|
||||
|c: [u8; 3]| OpaqueSurface::new(Vec3::new(srgb8_to_linear(c[0]), srgb8_to_linear(c[1]), srgb8_to_linear(c[2])), 0.0, 0.7, 1.5).unwrap();
|
||||
assert_eq!(task.looks.len() as u64, triangles);
|
||||
for look in &task.looks {
|
||||
let face = look.face.unwrap();
|
||||
let expected = if face % 2 == 0 { [face as u8 * 8, 100, 200] } else { [220; 3] };
|
||||
assert_eq!(look.surface, surface(expected), "face {face}");
|
||||
}
|
||||
}
|
||||
#[test]
|
||||
fn build_primary_and_shadows_share_cumulative_work_and_exhaust_atomically() {
|
||||
let mut task = triangle_task(CancelToken::default());
|
||||
let mut previous = 0;
|
||||
|
||||
Reference in New Issue
Block a user