use crate::evaluate::Constants;
use crate::{Error, Evaluation, Grid, Input, PixelShader, Views, evaluate_with};
use primase_foundations::parallel::Threads;
use std::collections::BTreeMap;
fn error(detail: impl Into<String>) -> Error {
Error {
bytecode: "evaluate_periodic",
at: 0,
detail: detail.into(),
}
}
pub fn evaluate_periodic(
shader: &dyn PixelShader,
constants: &Constants,
textures: &Views,
inputs: &BTreeMap<(String, u32), Input>,
cells: u32,
texels: u32,
targets: &[(u32, u8)],
) -> Result<Evaluation, Error> {
evaluate_periodic_with(
Threads::ONE,
shader,
constants,
textures,
inputs,
cells,
texels,
targets,
)
}
#[allow(clippy::too_many_arguments)]
pub fn evaluate_periodic_with(
threads: Threads,
shader: &dyn PixelShader,
constants: &Constants,
textures: &Views,
inputs: &BTreeMap<(String, u32), Input>,
cells: u32,
texels: u32,
targets: &[(u32, u8)],
) -> Result<Evaluation, Error> {
if cells < 2 {
return Err(error(format!("{cells} cells: a periodic bake needs at least 2")));
}
if texels == 0 {
return Err(error("a cell needs at least one texel"));
}
let size = cells
.checked_add(1)
.and_then(|c| c.checked_mul(texels))
.filter(|&n| n <= 4096)
.ok_or_else(|| {
error(format!(
"{} cells of {texels} texels need a grid of more than 4096",
u64::from(cells).saturating_add(1)
))
})?;
let period = size.checked_div(texels).unwrap_or(0) as f32;
let grid = evaluate_with(
threads,
shader,
constants,
textures,
inputs,
Grid {
size,
period: [period; 2],
},
targets,
)?;
let (size, out, texels) = (
size as usize,
cells.saturating_mul(texels) as usize,
texels as usize,
);
let weights: Vec<(f32, f32)> = (0..out)
.map(|x| {
if x < texels {
let s = (x as f32 + 0.5) / texels as f32;
(s, 1. - s)
} else {
(1., 0.)
}
})
.collect();
let mut targets = Vec::with_capacity(grid.targets.len());
for t in grid.targets {
let Some(e) = t else {
targets.push(None);
continue;
};
let at = |x: usize, y: usize| -> Result<[f32; 4], Error> {
e.get(y.saturating_mul(size).saturating_add(x))
.copied()
.ok_or_else(|| error(format!("texel ({x}, {y}) of a grid of {size} by {size}")))
};
let mut values = Vec::with_capacity(out.saturating_mul(out));
for (y, &(wy0, wy1)) in weights.iter().enumerate() {
for (x, &(wx0, wx1)) in weights.iter().enumerate() {
let mut v = at(x, y)?.map(|c| c * wx0 * wy0);
for (w, (px, py)) in [
(wx1 * wy0, (x.saturating_add(out), y)),
(wx0 * wy1, (x, y.saturating_add(out))),
(wx1 * wy1, (x.saturating_add(out), y.saturating_add(out))),
] {
if w != 0. {
for (sum, o) in v.iter_mut().zip(at(px, py)?) {
*sum += o * w;
}
}
}
values.push(v);
}
}
targets.push(Some(values));
}
Ok(Evaluation {
targets,
instructions: grid.instructions,
samples: grid.samples,
})
}