use crate::{
optics::raytracer::{
LightingPreset, apply_von_kries_white_balance, environment::environment_white_balance,
illuminant_temperature_k, integrate_channels_to_xyz_families,
},
renderer::{
buffers::{GpuGemMaterial, GpuTransportParams, transport_env_mode},
gpu::{environment_check::build_hdr_test_map, frame},
},
};
use glam::Vec3;
use super::{
MaterialForDispatch, SceneBuffersForDispatch, TransportDispatch, all_polished_finishes,
camera_params_for, dispatch_transport, dispatch_transport_for_class, round_brilliant_planes,
test_camera, tier3_material,
};
#[derive(Debug, Clone)]
pub struct SpectralDebugResult {
pub total_cases: usize,
pub max_self_consistency_ulp: u32,
pub over_budget_count: usize,
}
const SPECTRAL_DEBUG_ULP_BUDGET: u32 = 64;
const SPECTRAL_DEBUG_ABS_FLOOR: f32 = 1e-5;
#[must_use]
pub fn run_spectral_debug(ctx: &crate::renderer::gpu::GpuContext) -> SpectralDebugResult {
let mut max_ulp = 0u32;
let mut over_budget = 0usize;
let mut total_cases = 0usize;
let studio_total = run_spectral_debug_studio_case(ctx, &mut max_ulp, &mut over_budget);
total_cases += studio_total;
let hdr_total = run_spectral_debug_hdr_case(ctx, &mut max_ulp, &mut over_budget);
total_cases += hdr_total;
SpectralDebugResult {
total_cases,
max_self_consistency_ulp: max_ulp,
over_budget_count: over_budget,
}
}
fn run_spectral_debug_studio_case(
ctx: &crate::renderer::gpu::GpuContext,
max_ulp: &mut u32,
over_budget: &mut usize,
) -> usize {
let camera = test_camera();
let (width, height, samples) = (16u32, 16u32, 8u32);
let planes = round_brilliant_planes();
let material = tier3_material();
let gpu_material = GpuGemMaterial::encode(&material);
let temp_k = illuminant_temperature_k(LightingPreset::Daylight);
let wb = environment_white_balance(LightingPreset::Daylight.studio(1.0, 0.0, 0.0));
let camera_params = camera_params_for(&camera, width, height, samples);
let params = GpuTransportParams::new(
width * height,
10,
0,
transport_env_mode::STUDIO_RIG,
0.0,
temp_k,
1.0,
1.0,
0.0,
0.0,
wb.to_array(),
);
let total = (width * height * samples) as usize;
let finishes = all_polished_finishes(planes.len());
let dispatch = dispatch_transport(
ctx,
&camera_params,
¶ms,
&gpu_material,
&planes,
&finishes,
total,
);
accumulate_spectral_debug_case(&dispatch, total, Some(wb), max_ulp, over_budget);
total
}
fn run_spectral_debug_hdr_case(
ctx: &crate::renderer::gpu::GpuContext,
max_ulp: &mut u32,
over_budget: &mut usize,
) -> usize {
let camera = test_camera();
let (width, height, samples) = (16u32, 16u32, 8u32);
let planes = round_brilliant_planes();
let material = tier3_material();
let gpu_material = GpuGemMaterial::encode(&material);
let camera_params = camera_params_for(&camera, width, height, samples);
let params = GpuTransportParams::new(
width * height,
10,
0,
transport_env_mode::HDR_MAP,
0.0,
0.0,
1.0,
1.0,
0.0,
0.0,
Vec3::ONE.to_array(),
);
let total = (width * height * samples) as usize;
let finishes = all_polished_finishes(planes.len());
let map = build_hdr_test_map();
let hdr_env = crate::renderer::env_map_gpu::HdrEnvGpuData::upload(&ctx.device, &map);
let dispatch = dispatch_transport_for_class(
ctx,
&camera_params,
¶ms,
MaterialForDispatch {
encoded: &gpu_material,
class: frame::material_class::GENERIC,
},
SceneBuffersForDispatch {
planes: &planes,
facet_finishes: &finishes,
hdr_env: Some(&hdr_env),
},
total,
);
accumulate_spectral_debug_case(&dispatch, total, None, max_ulp, over_budget);
total
}
fn accumulate_spectral_debug_case(
dispatch: &TransportDispatch,
total: usize,
wb: Option<Vec3>,
max_ulp: &mut u32,
over_budget: &mut usize,
) {
for idx in 0..total {
let mut radiance = [0.0f32; 8];
let mut lambdas = [0.0f32; 8];
let mut path_pdf = [0.0f32; 8];
let mut compat = [0u8; 8];
radiance.copy_from_slice(&dispatch.radiance[idx * 8..idx * 8 + 8]);
lambdas.copy_from_slice(&dispatch.lambdas[idx * 8..idx * 8 + 8]);
path_pdf.copy_from_slice(&dispatch.path_pdf[idx * 8..idx * 8 + 8]);
for (c, &raw) in compat
.iter_mut()
.zip(&dispatch.compat[idx * 8..idx * 8 + 8])
{
*c = u8::try_from(raw).expect("the kernel's compat flags are small bit sets");
}
let integrated =
integrate_channels_to_xyz_families(&radiance, &lambdas, &path_pdf, 0, compat);
let recombined = wb
.map_or(integrated, |wb| {
apply_von_kries_white_balance(integrated, wb)
})
.max(Vec3::ZERO);
let gpu_xyz = Vec3::new(
dispatch.xyz[idx * 3],
dispatch.xyz[idx * 3 + 1],
dispatch.xyz[idx * 3 + 2],
);
for (cpu, gpu) in [
(recombined.x, gpu_xyz.x),
(recombined.y, gpu_xyz.y),
(recombined.z, gpu_xyz.z),
] {
let ulp = crate::renderer::gpu::ulp::ulp_distance(cpu, gpu);
if !crate::renderer::gpu::ulp::within_tolerance(
cpu,
gpu,
SPECTRAL_DEBUG_ULP_BUDGET,
SPECTRAL_DEBUG_ABS_FLOOR,
) {
*over_budget += 1;
*max_ulp = (*max_ulp).max(ulp);
}
}
}
}
impl SpectralDebugResult {
#[must_use]
pub const fn passed(&self) -> bool {
self.over_budget_count == 0
}
}