use crate::renderer::gpu::{
compute,
ulp::{ulp_distance, within_tolerance},
};
mod absorption_pleochroism;
mod bounce;
mod dispersion;
mod eigenmodes_biaxial;
mod eigenmodes_uniaxial;
mod frame_and_tir;
mod fresnel;
mod nee;
mod p2_uniaxial_fresnel;
mod p6_exit_splitting;
mod scattering;
pub use nee::{
BalanceHeuristicCase, Dist1dFindBucketCase, Dist2dPdfCase, Dist2dSampleCase,
NeeFrostedExteriorCase, NeeHgScatterCase, run_balance_heuristic, run_dist1d_find_bucket,
run_dist2d_pdf, run_dist2d_sample, run_nee_frosted_exterior, run_nee_hg_scatter,
};
pub use absorption_pleochroism::{
AbsorptionBandGpu, AbsorptionCase, AssignedModeAlphaUniaxialCase, PleochroicCase,
run_absorption, run_assigned_mode_alpha_uniaxial, run_pleochroic,
};
pub use bounce::{
CosineHemisphereCase, FrostedBounceCase, run_cosine_hemisphere, run_frosted_bounce,
};
pub use dispersion::{DispersionCase, run_dispersion};
pub use eigenmodes_biaxial::{
AssignedModeAlphaBiaxialCase, BiaxialEigenPolarizationCase, BiaxialModePoyntingCase,
BiaxialPleochroicCase, BiaxialResolveEntryModeCase, BiaxialWaveIndicesCase,
run_assigned_mode_alpha_biaxial, run_biaxial_eigen_polarization, run_biaxial_mode_poynting,
run_biaxial_pleochroic, run_biaxial_resolve_entry_mode, run_biaxial_wave_indices,
};
pub use eigenmodes_uniaxial::{
EigenPolarizationCase, PerModeIndexCase, ThetaCCase, WalkOffCase, run_eigen_polarization,
run_per_mode_index, run_theta_c, run_walk_off,
};
pub use frame_and_tir::{
FrameRotationCase, SignedPsiCase, TirPhaseDeltaCase, TirRetardationCase, run_frame_rotation,
run_signed_psi, run_tir_phase_delta, run_tir_retardation,
};
pub use fresnel::{
FresnelReflectionCase, FresnelTransmissionCase, run_fresnel_reflection,
run_fresnel_transmission,
};
pub use p2_uniaxial_fresnel::{
EntrySolvePairCase, InternalSolveCase, run_entry_solve_pair, run_internal_solve,
};
pub use p6_exit_splitting::{
ChannelTransmissionCase, NarrowCompatCase, NarrowCompatResult, UniaxialExitTransmissionCase,
run_channel_transmission, run_narrow_compat, run_uniaxial_exit_transmission,
};
pub use scattering::{
HgPhaseCase, HgSampleCase, ScatterOrExtinguishCase, run_hg_phase, run_hg_sample,
run_scatter_or_extinguish,
};
const SHADER_SRC: &str = include_str!(concat!(
env!("OUT_DIR"),
"/transport_functions.generated.wgsl"
));
#[derive(Debug, Clone)]
pub struct UlpCheckResult<Case: Clone> {
pub label: &'static str,
pub total_comparisons: usize,
pub budget: u32,
pub abs_floor: f32,
pub max_ulp: u32,
pub max_raw_ulp: u32,
pub over_budget_count: usize,
pub exempted_count: usize,
pub argmax: Option<UlpArgmax<Case>>,
}
#[derive(Debug, Clone)]
pub struct UlpArgmax<Case: Clone> {
pub case: Case,
pub component: &'static str,
pub cpu: f32,
pub gpu: f32,
pub ulp: u32,
}
impl<Case: Clone> UlpCheckResult<Case> {
#[must_use]
pub const fn passed(&self) -> bool {
self.over_budget_count == 0
}
}
struct UlpAccumulator<Case: Clone> {
label: &'static str,
budget: u32,
abs_floor: f32,
total: usize,
max_ulp: u32,
max_raw_ulp: u32,
over_budget: usize,
exempted: usize,
argmax: Option<UlpArgmax<Case>>,
}
impl<Case: Clone> UlpAccumulator<Case> {
const fn new(label: &'static str, budget: u32, abs_floor: f32) -> Self {
Self {
label,
budget,
abs_floor,
total: 0,
max_ulp: 0,
max_raw_ulp: 0,
over_budget: 0,
exempted: 0,
argmax: None,
}
}
fn record(&mut self, case: &Case, component: &'static str, cpu: f32, gpu: f32) {
self.total += 1;
let ulp = ulp_distance(cpu, gpu);
if ulp > self.max_raw_ulp {
self.max_raw_ulp = ulp;
}
let within = within_tolerance(cpu, gpu, self.budget, self.abs_floor);
if !within {
self.over_budget += 1;
if ulp > self.max_ulp {
self.max_ulp = ulp;
self.argmax = Some(UlpArgmax {
case: case.clone(),
component,
cpu,
gpu,
ulp,
});
}
} else if ulp > self.budget {
self.exempted += 1;
}
}
fn finish(self) -> UlpCheckResult<Case> {
UlpCheckResult {
label: self.label,
total_comparisons: self.total,
budget: self.budget,
abs_floor: self.abs_floor,
max_ulp: self.max_ulp,
max_raw_ulp: self.max_raw_ulp,
over_budget_count: self.over_budget,
exempted_count: self.exempted,
argmax: self.argmax,
}
}
}
const STOKES_SAMPLES: [[f32; 4]; 5] = [
[1.0, 0.0, 0.0, 0.0],
[1.0, 1.0, 0.0, 0.0],
[1.0, 0.0, 1.0, 0.0],
[1.0, 0.0, 0.0, 1.0],
[1.0, 0.3, -0.4, 0.2],
];
struct StokesCaseBankConfig {
entry_point: &'static str,
in_binding: u32,
out_binding: u32,
budget: u32,
abs_floor: f32,
}
fn run_stokes_case_bank<C: bytemuck::Pod + Clone + std::fmt::Debug>(
ctx: &crate::renderer::gpu::GpuContext,
config: &StokesCaseBankConfig,
cases: &[C],
cpu_fn: impl Fn(&C) -> [f32; 4],
) -> UlpCheckResult<C> {
let total = cases.len();
let in_buf = compute::upload(
&ctx.device,
"transport case in",
cases,
wgpu::BufferUsages::STORAGE,
);
let out_buf = compute::zeroed_buffer::<f32>(
&ctx.device,
"transport case out",
total * 4,
wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_SRC,
);
let pipeline = compute::create_compute_pipeline(
&ctx.device,
config.entry_point,
SHADER_SRC,
config.entry_point,
);
let bind_group = compute::bind_buffers(
&ctx.device,
"transport case bind group",
&pipeline,
&[(config.in_binding, &in_buf), (config.out_binding, &out_buf)],
);
let workgroups = u32::try_from(total)
.expect("the dispatched case count fits in u32")
.div_ceil(64);
compute::dispatch_and_wait(
&ctx.device,
&ctx.queue,
&pipeline,
&bind_group,
(workgroups, 1, 1),
);
let gpu_out: Vec<f32> = compute::readback(&ctx.device, &ctx.queue, &out_buf, total * 4);
let mut acc = UlpAccumulator::new(
entry_point_label(config.entry_point),
config.budget,
config.abs_floor,
);
for (idx, case) in cases.iter().enumerate() {
let cpu = cpu_fn(case);
for (c_idx, comp) in ["i", "q", "u", "v"].iter().enumerate() {
acc.record(case, comp, cpu[c_idx], gpu_out[idx * 4 + c_idx]);
}
}
acc.finish()
}
const fn entry_point_label(entry_point: &str) -> &'static str {
match entry_point.as_bytes() {
b"frame_rotation_main" => "frame_rotation",
b"fresnel_reflection_main" => "fresnel_reflection",
b"fresnel_transmission_main" => "fresnel_transmission",
b"tir_retardation_main" => "tir_retardation",
_ => "unknown",
}
}