use glam::Vec3;
use crate::optics::{
materials::GemMaterial,
raytracer::{Camera, LightingPreset},
};
use super::{
CHUNK_BUDGET_BYTES, FLOATS_PER_TUPLE, GpuFrameScene, GpuPipelineKind, classify_material,
dispatch::chunk_pixels_for, material_class, renderer::GpuFrameRenderer,
};
pub struct ChunkEquivalenceResult {
pub chunks_forced: usize,
pub differing_pixels: usize,
pub total_pixels: usize,
pub max_abs_diff: f32,
}
impl ChunkEquivalenceResult {
#[must_use]
pub const fn passed(&self) -> bool {
self.chunks_forced > 1 && self.differing_pixels == 0
}
}
#[must_use]
pub fn run_chunk_equivalence(renderer: &mut GpuFrameRenderer) -> ChunkEquivalenceResult {
use crate::geometry::cuts::StandardGemCuts;
let camera = Camera::new(0.35, 0.28, 5.0, 18.0);
let planes = StandardGemCuts::standard_round_brilliant();
let material = GemMaterial::by_name("Spinel").expect("Spinel is a built-in cubic material");
let (width, height) = (64u32, 64u32);
let spp = 2u32;
let num_pixels = (width * height) as usize;
let scene = GpuFrameScene {
camera: &camera,
width,
height,
planes: &planes,
facet_finishes: &[],
material: &material,
max_bounces: 8,
environment: LightingPreset::Daylight.studio(1.0, 0.4, 0.35),
};
let mut whole = vec![Vec3::ZERO; num_pixels];
renderer.set_chunk_budget_bytes(CHUNK_BUDGET_BYTES);
renderer
.accumulate(&scene, 0, spp, &mut whole)
.expect("a cubic material under the studio rig is GPU-supported");
let budget = 700 * FLOATS_PER_TUPLE * size_of::<f32>();
let chunk_pixels = chunk_pixels_for(budget, spp, num_pixels);
let chunks_forced = num_pixels.div_ceil(chunk_pixels);
let mut chunked = vec![Vec3::ZERO; num_pixels];
renderer.set_chunk_budget_bytes(budget);
renderer
.accumulate(&scene, 0, spp, &mut chunked)
.expect("a cubic material under the studio rig is GPU-supported");
renderer.set_chunk_budget_bytes(CHUNK_BUDGET_BYTES);
let mut differing_pixels = 0usize;
let mut max_abs_diff = 0.0f32;
for (a, b) in whole.iter().zip(&chunked) {
if a.to_array()
.iter()
.zip(b.to_array().iter())
.any(|(x, y)| x.to_bits() != y.to_bits())
{
differing_pixels += 1;
max_abs_diff = max_abs_diff.max((*a - *b).abs().max_element());
}
}
ChunkEquivalenceResult {
chunks_forced,
differing_pixels,
total_pixels: num_pixels,
max_abs_diff,
}
}
pub struct PipelineEquivalenceResult {
pub differing_pixels: usize,
pub total_pixels: usize,
pub max_abs_diff: f32,
}
impl PipelineEquivalenceResult {
#[must_use]
pub fn passed(&self) -> bool {
self.differing_pixels == 0 || self.max_abs_diff <= 5e-6
}
}
#[must_use]
pub fn run_pipeline_equivalence(renderer: &mut GpuFrameRenderer) -> PipelineEquivalenceResult {
use crate::geometry::cuts::StandardGemCuts;
let camera = Camera::new(0.35, 0.28, 5.0, 18.0);
let planes = StandardGemCuts::standard_round_brilliant();
let material = GemMaterial::by_name("Spinel").expect("Spinel is a built-in cubic material");
let (width, height) = (32u32, 32u32);
let spp = 2u32;
let num_pixels = (width * height) as usize;
let scene = GpuFrameScene {
camera: &camera,
width,
height,
planes: &planes,
facet_finishes: &[],
material: &material,
max_bounces: 8,
environment: LightingPreset::Daylight.studio(1.0, 0.4, 0.35),
};
renderer.set_pipeline_kind(GpuPipelineKind::Megakernel);
let mut megakernel = vec![Vec3::ZERO; num_pixels];
renderer
.accumulate(&scene, 0, spp, &mut megakernel)
.expect("a cubic material under the studio rig is GPU-supported");
renderer.set_pipeline_kind(GpuPipelineKind::Wavefront);
let mut wavefront = vec![Vec3::ZERO; num_pixels];
renderer
.accumulate(&scene, 0, spp, &mut wavefront)
.expect("a cubic material under the studio rig is GPU-supported");
renderer.set_pipeline_kind(GpuPipelineKind::Megakernel);
let mut differing_pixels = 0usize;
let mut max_abs_diff = 0.0f32;
for (a, b) in megakernel.iter().zip(&wavefront) {
if a.to_array()
.iter()
.zip(b.to_array().iter())
.any(|(x, y)| x.to_bits() != y.to_bits())
{
differing_pixels += 1;
max_abs_diff = max_abs_diff.max((*a - *b).abs().max_element());
}
}
PipelineEquivalenceResult {
differing_pixels,
total_pixels: num_pixels,
max_abs_diff,
}
}
#[must_use]
pub fn run_wavefront_determinism(renderer: &mut GpuFrameRenderer) -> PipelineEquivalenceResult {
use crate::geometry::cuts::StandardGemCuts;
let camera = Camera::new(0.35, 0.28, 5.0, 18.0);
let planes = StandardGemCuts::standard_round_brilliant();
let material = GemMaterial::by_name("Spinel").expect("Spinel is a built-in cubic material");
let (width, height) = (32u32, 32u32);
let spp = 2u32;
let num_pixels = (width * height) as usize;
let scene = GpuFrameScene {
camera: &camera,
width,
height,
planes: &planes,
facet_finishes: &[],
material: &material,
max_bounces: 8,
environment: LightingPreset::Daylight.studio(1.0, 0.4, 0.35),
};
renderer.set_pipeline_kind(GpuPipelineKind::Wavefront);
let mut first = vec![Vec3::ZERO; num_pixels];
renderer
.accumulate(&scene, 0, spp, &mut first)
.expect("a cubic material under the studio rig is GPU-supported");
let mut second = vec![Vec3::ZERO; num_pixels];
renderer
.accumulate(&scene, 0, spp, &mut second)
.expect("a cubic material under the studio rig is GPU-supported");
renderer.set_pipeline_kind(GpuPipelineKind::Megakernel);
let mut differing_pixels = 0usize;
let mut max_abs_diff = 0.0f32;
for (a, b) in first.iter().zip(&second) {
if a.to_array()
.iter()
.zip(b.to_array().iter())
.any(|(x, y)| x.to_bits() != y.to_bits())
{
differing_pixels += 1;
max_abs_diff = max_abs_diff.max((*a - *b).abs().max_element());
}
}
PipelineEquivalenceResult {
differing_pixels,
total_pixels: num_pixels,
max_abs_diff,
}
}
#[derive(Debug, Clone)]
pub struct SpecialisationCaseResult {
pub material_name: String,
pub material_class: u32,
pub self_determinism_differing_pixels: usize,
pub generic_vs_specialised_differing_pixels: usize,
pub total_pixels: usize,
pub generic_vs_specialised_max_abs_diff: f32,
}
impl SpecialisationCaseResult {
#[must_use]
pub const fn passed(&self) -> bool {
self.self_determinism_differing_pixels == 0
}
}
pub struct SpecialisationEquivalenceResult {
pub cases: Vec<SpecialisationCaseResult>,
}
impl SpecialisationEquivalenceResult {
#[must_use]
pub fn passed(&self) -> bool {
!self.cases.is_empty() && self.cases.iter().all(SpecialisationCaseResult::passed)
}
}
#[must_use]
pub fn run_specialisation_equivalence(
renderer: &mut GpuFrameRenderer,
) -> SpecialisationEquivalenceResult {
use crate::geometry::cuts::StandardGemCuts;
let camera = Camera::new(0.35, 0.28, 5.0, 18.0);
let planes = StandardGemCuts::standard_round_brilliant();
let (width, height) = (32u32, 32u32);
let spp = 3u32;
let num_pixels = (width * height) as usize;
let environment = LightingPreset::Daylight.studio(1.0, 0.4, 0.35);
let representative_materials = ["Diamond", "Zircon", "Alexandrite"];
let mut cases = Vec::with_capacity(representative_materials.len());
for name in representative_materials {
let material = GemMaterial::by_name(name).unwrap_or_else(|| {
panic!("{name:?} is a built-in material in GemMaterial::all_materials()")
});
let scene = GpuFrameScene {
camera: &camera,
width,
height,
planes: &planes,
facet_finishes: &[],
material: &material,
max_bounces: 8,
environment,
};
let class = classify_material(&material);
let mut run1 = vec![Vec3::ZERO; num_pixels];
renderer
.accumulate_via_pipeline(&scene, class, 0, spp, &mut run1, None)
.expect("every representative material here is GPU-supported under the studio rig");
let mut run2 = vec![Vec3::ZERO; num_pixels];
renderer
.accumulate_via_pipeline(&scene, class, 0, spp, &mut run2, None)
.expect("every representative material here is GPU-supported under the studio rig");
let self_determinism_differing_pixels = run1
.iter()
.zip(&run2)
.filter(|(a, b)| {
a.to_array()
.iter()
.zip(b.to_array().iter())
.any(|(x, y)| x.to_bits() != y.to_bits())
})
.count();
let mut generic = vec![Vec3::ZERO; num_pixels];
renderer
.accumulate_via_pipeline(&scene, material_class::GENERIC, 0, spp, &mut generic, None)
.expect("every representative material here is GPU-supported under the studio rig");
let mut generic_vs_specialised_differing_pixels = 0usize;
let mut generic_vs_specialised_max_abs_diff = 0.0f32;
for (a, b) in generic.iter().zip(&run1) {
if a.to_array()
.iter()
.zip(b.to_array().iter())
.any(|(x, y)| x.to_bits() != y.to_bits())
{
generic_vs_specialised_differing_pixels += 1;
generic_vs_specialised_max_abs_diff =
generic_vs_specialised_max_abs_diff.max((*a - *b).abs().max_element());
}
}
cases.push(SpecialisationCaseResult {
material_name: name.to_string(),
material_class: class,
self_determinism_differing_pixels,
generic_vs_specialised_differing_pixels,
total_pixels: num_pixels,
generic_vs_specialised_max_abs_diff,
});
}
SpecialisationEquivalenceResult { cases }
}