use crate::{
optics::raytracer::Camera,
renderer::{
buffers::GpuRay,
gpu::{
compute,
ulp::{ulp_distance, within_tolerance},
},
},
};
const SHADER_SRC: &str = include_str!("../shaders/camera_ray.wgsl");
#[repr(C)]
#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)]
pub struct CameraRayCase {
pub yaw: f32,
pub pitch: f32,
pub distance: f32,
pub fov_deg: f32,
pub screen_x: f32,
pub screen_y: f32,
pub width: f32,
pub height: f32,
pub jitter_x: f32,
pub jitter_y: f32,
_pad0: f32,
_pad1: f32,
}
const _: () = assert!(size_of::<CameraRayCase>() == 48);
#[derive(Clone, Copy)]
struct CameraPose {
yaw: f32,
pitch: f32,
distance: f32,
fov_deg: f32,
}
#[derive(Clone, Copy)]
struct ScreenSample {
screen_x: f32,
screen_y: f32,
width: f32,
height: f32,
jitter_x: f32,
jitter_y: f32,
}
impl CameraRayCase {
#[must_use]
const fn new(pose: CameraPose, screen: ScreenSample) -> Self {
Self {
yaw: pose.yaw,
pitch: pose.pitch,
distance: pose.distance,
fov_deg: pose.fov_deg,
screen_x: screen.screen_x,
screen_y: screen.screen_y,
width: screen.width,
height: screen.height,
jitter_x: screen.jitter_x,
jitter_y: screen.jitter_y,
_pad0: 0.0,
_pad1: 0.0,
}
}
fn cpu_ray(self) -> crate::optics::raytracer::Ray {
let camera = Camera::new(self.yaw, self.pitch, self.distance, self.fov_deg);
camera.generate_ray(
self.screen_x,
self.screen_y,
self.width,
self.height,
self.jitter_x,
self.jitter_y,
)
}
}
#[must_use]
pub fn build_cases() -> Vec<CameraRayCase> {
let mut cases = dense_cases();
cases.extend(adversarial_cases());
cases
}
fn dense_cases() -> Vec<CameraRayCase> {
let mut cases = Vec::new();
let yaws: Vec<f32> = (0..9)
.map(|i| (i as f32).mul_add(2.0 * std::f32::consts::PI / 8.0, -std::f32::consts::PI))
.collect();
let pitches: Vec<f32> = (0..9)
.map(|i| (i as f32).mul_add(3.10 / 8.0, -1.55))
.collect();
let distances = [1.0f32, 3.0, 8.0];
let fovs = [15.0f32, 45.0, 90.0, 140.0];
let (width, height) = (64.0f32, 64.0f32);
let screen_positions = [
(0.0f32, 0.0f32),
(63.0, 0.0),
(0.0, 63.0),
(63.0, 63.0),
(32.0, 32.0),
];
let jitters = [(-0.499f32, -0.499f32), (0.0, 0.0), (0.499, 0.499)];
for &yaw in &yaws {
for &pitch in &pitches {
for &distance in &distances {
for &fov_deg in &fovs {
for &(sx, sy) in &screen_positions {
for &(jx, jy) in &jitters {
cases.push(CameraRayCase::new(
CameraPose {
yaw,
pitch,
distance,
fov_deg,
},
ScreenSample {
screen_x: sx,
screen_y: sy,
width,
height,
jitter_x: jx,
jitter_y: jy,
},
));
}
}
}
}
}
}
cases
}
fn adversarial_cases() -> Vec<CameraRayCase> {
let mut cases = Vec::new();
for &sign in &[1.0f32, -1.0] {
for &yaw in &[0.0f32, std::f32::consts::PI] {
for &eps in &[1e-5f32, -1e-5, 9e-5, -9e-5] {
let pitch = f32::mul_add(sign, std::f32::consts::FRAC_PI_2, eps);
cases.push(CameraRayCase::new(
CameraPose {
yaw,
pitch,
distance: 4.0,
fov_deg: 60.0,
},
ScreenSample {
screen_x: 32.0,
screen_y: 32.0,
width: 64.0,
height: 64.0,
jitter_x: 0.0,
jitter_y: 0.0,
},
));
}
}
}
for &(sx, sy) in &[(0.0f32, 0.0f32), (63.0, 63.0), (32.0, 32.0)] {
for &(jx, jy) in &[(-0.5f32, -0.5f32), (0.4999, 0.4999), (-0.5, 0.4999)] {
cases.push(CameraRayCase::new(
CameraPose {
yaw: 0.6,
pitch: 0.4,
distance: 5.0,
fov_deg: 50.0,
},
ScreenSample {
screen_x: sx,
screen_y: sy,
width: 64.0,
height: 64.0,
jitter_x: jx,
jitter_y: jy,
},
));
}
}
for &fov_deg in &[1.0f32, 170.0] {
cases.push(CameraRayCase::new(
CameraPose {
yaw: 0.3,
pitch: 0.2,
distance: 5.0,
fov_deg,
},
ScreenSample {
screen_x: 32.0,
screen_y: 32.0,
width: 64.0,
height: 64.0,
jitter_x: 0.0,
jitter_y: 0.0,
},
));
}
cases
}
#[derive(Debug, Clone, Copy)]
pub struct CameraRayUlpArgmax {
pub case_index: usize,
pub case: CameraRayCase,
pub component: &'static str,
pub cpu: f32,
pub gpu: f32,
pub ulp: u32,
}
#[derive(Debug, Clone, Copy)]
pub struct CameraRayCheckResult {
pub total_cases: usize,
pub budget: u32,
pub abs_floor: f32,
pub max_ulp: u32,
pub max_raw_ulp: u32,
pub argmax: Option<CameraRayUlpArgmax>,
pub over_budget_count: usize,
pub exempted_count: usize,
}
impl CameraRayCheckResult {
#[must_use]
pub const fn passed(&self) -> bool {
self.over_budget_count == 0
}
}
pub const CAMERA_RAY_ULP_BUDGET: u32 = 64;
pub const CAMERA_RAY_ABS_FLOOR: f32 = 1e-4;
#[must_use]
pub fn run(ctx: &crate::renderer::gpu::GpuContext) -> CameraRayCheckResult {
let cases = build_cases();
let total = cases.len();
let cases_buf = compute::upload(
&ctx.device,
"camera_ray cases",
&cases,
wgpu::BufferUsages::STORAGE,
);
let out_buf = compute::zeroed_buffer::<GpuRay>(
&ctx.device,
"camera_ray output",
total,
wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_SRC,
);
let pipeline = compute::create_compute_pipeline(&ctx.device, "camera_ray", SHADER_SRC, "main");
let bind_group = compute::bind_buffers(
&ctx.device,
"camera_ray bind group",
&pipeline,
&[(0, &cases_buf), (1, &out_buf)],
);
let workgroups = (total as u32).div_ceil(64);
compute::dispatch_and_wait(
&ctx.device,
&ctx.queue,
&pipeline,
&bind_group,
(workgroups, 1, 1),
);
let gpu_rays: Vec<GpuRay> = compute::readback(&ctx.device, &ctx.queue, &out_buf, total);
let mut max_ulp = 0u32;
let mut max_raw_ulp = 0u32;
let mut argmax = None;
let mut over_budget_count = 0usize;
let mut exempted_count = 0usize;
for (idx, (&case, gpu_ray)) in cases.iter().zip(gpu_rays.iter()).enumerate() {
let cpu_ray = case.cpu_ray();
let components: [(&'static str, f32, f32); 6] = [
("origin.x", cpu_ray.origin.x, gpu_ray.origin[0]),
("origin.y", cpu_ray.origin.y, gpu_ray.origin[1]),
("origin.z", cpu_ray.origin.z, gpu_ray.origin[2]),
("dir.x", cpu_ray.dir.x, gpu_ray.dir[0]),
("dir.y", cpu_ray.dir.y, gpu_ray.dir[1]),
("dir.z", cpu_ray.dir.z, gpu_ray.dir[2]),
];
for (component, cpu, gpu) in components {
let ulp = ulp_distance(cpu, gpu);
if ulp > max_raw_ulp {
max_raw_ulp = ulp;
}
if within_tolerance(cpu, gpu, CAMERA_RAY_ULP_BUDGET, CAMERA_RAY_ABS_FLOOR) {
if ulp > CAMERA_RAY_ULP_BUDGET {
exempted_count += 1;
}
} else {
over_budget_count += 1;
if ulp > max_ulp {
max_ulp = ulp;
argmax = Some(CameraRayUlpArgmax {
case_index: idx,
case,
component,
cpu,
gpu,
ulp,
});
}
}
}
}
CameraRayCheckResult {
total_cases: total,
budget: CAMERA_RAY_ULP_BUDGET,
abs_floor: CAMERA_RAY_ABS_FLOOR,
max_raw_ulp,
exempted_count,
max_ulp,
argmax,
over_budget_count,
}
}