use crate::{color::cie1931::cie_1931_cmf, renderer::gpu::compute};
use super::{SHADER_SRC, UlpAccumulator, UlpCheckResult};
pub const CMF_ULP_BUDGET: u32 = 32;
pub const CMF_ABS_FLOOR: f32 = 1e-6;
#[must_use]
pub fn build_cmf_lambdas() -> Vec<f32> {
let mut lambdas = Vec::new();
#[expect(
clippy::cast_possible_truncation,
clippy::cast_sign_loss,
reason = "the constant (850 - 300) / 0.5 = 1100 is positive and far below u32::MAX"
)]
let steps = ((850.0 - 300.0) / 0.5) as u32;
for step in 0..=steps {
lambdas.push((step as f32).mul_add(0.5, 300.0));
}
lambdas.push(380.0);
lambdas.push(780.0);
for node in 0..81u32 {
let lambda = (node as f32).mul_add(5.0, 380.0);
lambdas.push(lambda);
if node < 80 {
lambdas.push(lambda + 2.5);
}
}
for &lambda in &[379.999f32, 380.001, 779.999, 780.001, -100.0, 2000.0] {
lambdas.push(lambda);
}
lambdas
}
#[must_use]
pub fn run_cmf(ctx: &crate::renderer::gpu::GpuContext) -> UlpCheckResult<f32> {
let lambdas = build_cmf_lambdas();
let total = lambdas.len();
let in_buf = compute::upload(
&ctx.device,
"cmf lambdas",
&lambdas,
wgpu::BufferUsages::STORAGE,
);
let out_buf = compute::zeroed_buffer::<f32>(
&ctx.device,
"cmf out",
total * 3,
wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_SRC,
);
let pipeline =
compute::create_compute_pipeline(&ctx.device, "cmf_main", SHADER_SRC, "cmf_main");
let bind_group = compute::bind_buffers(
&ctx.device,
"cmf bind group",
&pipeline,
&[(0, &in_buf), (1, &out_buf)],
);
let workgroups = u32::try_from(total)
.expect("a check case count fits u32")
.div_ceil(64);
compute::dispatch_and_wait(
&ctx.device,
&ctx.queue,
&pipeline,
&bind_group,
(workgroups, 1, 1),
);
let gpu_xyz: Vec<f32> = compute::readback(&ctx.device, &ctx.queue, &out_buf, total * 3);
let mut acc = UlpAccumulator::new("cie_1931_cmf", CMF_ULP_BUDGET, CMF_ABS_FLOOR);
for (idx, &lambda) in lambdas.iter().enumerate() {
let cpu = cie_1931_cmf(lambda);
acc.record(&lambda, "x", cpu[0], gpu_xyz[idx * 3]);
acc.record(&lambda, "y", cpu[1], gpu_xyz[idx * 3 + 1]);
acc.record(&lambda, "z", cpu[2], gpu_xyz[idx * 3 + 2]);
}
acc.finish()
}