use crate::{Absent, Error, PixelShader, Views};
use iridia::{ConstantType, D3dClass, Interface, Member, ResourceKind};
use std::collections::BTreeMap;
use primase_foundations::parallel::Threads;
fn error(at: usize, detail: impl Into<String>) -> Error {
Error {
bytecode: "evaluate",
at,
detail: detail.into(),
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Field {
pub buffer: u32,
pub offset: u32,
pub words: u32,
}
#[derive(Clone, Debug, Default)]
pub struct Layout {
pub buffers: Vec<(u32, u32)>,
pub fields: BTreeMap<String, Field>,
}
impl Layout {
pub fn of(shader: &dyn PixelShader) -> Result<Self, Error> {
let mut layout = Self {
buffers: shader
.constant_buffers()
.iter()
.map(|&(slot, rows)| {
rows.checked_mul(4)
.map(|words| (slot, words))
.ok_or_else(|| error(0, "constant buffer size overflow"))
})
.collect::<Result<_, _>>()?,
fields: BTreeMap::new(),
};
layout.add_named(shader.interface())?;
Ok(layout)
}
fn add_named(&mut self, interface: &Interface) -> Result<(), Error> {
for resource in &interface.resources {
let ResourceKind::ConstantBuffer { layout } = &resource.kind else {
continue;
};
let slot = resource
.provenance
.d3d
.filter(|b| b.class == D3dClass::ConstantBuffer)
.ok_or_else(|| {
error(
0,
format!(
"buffer {} has no constant-buffer register",
resource.provenance.name
),
)
})?
.register;
for c in &layout.constants {
match c.ty.members() {
[] => self.insert(slot, &c.name, c.offset, words(&c.ty)?)?,
members => self.members(slot, c.offset, members)?,
}
}
}
Ok(())
}
fn members(&mut self, slot: u32, offset: u32, members: &[Member]) -> Result<(), Error> {
for m in members {
let at = offset
.checked_add(m.offset)
.ok_or_else(|| error(0, "member offset overflow"))?;
match m.ty.members() {
[] => self.insert(slot, &m.name, at, words(&m.ty)?)?,
inner => self.members(slot, at, inner)?,
}
}
Ok(())
}
fn insert(&mut self, buffer: u32, name: &str, offset: u32, words: u32) -> Result<(), Error> {
if !offset.is_multiple_of(4) {
return Err(error(
offset as usize,
format!("field {name} is not word-aligned"),
));
}
let field = Field {
buffer,
offset: offset / 4,
words,
};
if self.fields.insert(name.to_owned(), field).is_some() {
return Err(error(offset as usize, format!("field name {name} occurs twice")));
}
Ok(())
}
}
fn words(ty: &ConstantType) -> Result<u32, Error> {
ty.size()
.map(|bytes| bytes.div_ceil(4))
.ok_or_else(|| error(0, "a constant's layout overflows"))
}
#[derive(Clone, Debug)]
pub struct Constants {
fields: BTreeMap<String, Field>,
buffers: Vec<(u32, Vec<[u32; 4]>)>,
written: Vec<Vec<bool>>,
}
impl Constants {
pub fn new(layout: &Layout) -> Self {
Self {
fields: layout.fields.clone(),
buffers: layout
.buffers
.iter()
.map(|&(slot, words)| (slot, vec![[0; 4]; words.div_ceil(4) as usize]))
.collect(),
written: layout
.buffers
.iter()
.map(|&(_, words)| vec![false; words.div_ceil(4) as usize * 4])
.collect(),
}
}
pub fn set(&mut self, field: &str, values: &[f32]) -> Result<bool, Error> {
let Some(&f) = self.fields.get(field) else {
return Ok(false);
};
if values.len() > f.words as usize {
return Err(error(
0,
format!("{} values for {field}, which has {} words", values.len(), f.words),
));
}
if let Some(b) = self.buffers.iter().position(|(slot, _)| *slot == f.buffer) {
let words = self.written.get(b).map_or(0, Vec::len);
for (i, v) in values.iter().enumerate() {
let word = (f.offset as usize).saturating_add(i);
if word < words {
self.store(b, word, v.to_bits());
}
}
}
Ok(true)
}
pub fn set_words(&mut self, buffer: u32, word_offset: u32, words: &[u32]) -> Result<(), Error> {
let b = self
.buffers
.iter()
.position(|(slot, _)| *slot == buffer)
.ok_or_else(|| error(0, format!("the program reads no cb{buffer}")))?;
let end = (word_offset as usize)
.checked_add(words.len())
.filter(|&end| end <= self.written.get(b).map_or(0, Vec::len))
.ok_or_else(|| error(word_offset as usize, format!("words past the end of cb{buffer}")))?;
for (word, &bits) in (word_offset as usize..end).zip(words) {
self.store(b, word, bits);
}
Ok(())
}
fn store(&mut self, b: usize, word: usize, bits: u32) {
if let Some(slot) = self
.buffers
.get_mut(b)
.and_then(|(_, rows)| rows.get_mut(word / 4))
.and_then(|row| row.get_mut(word % 4))
{
*slot = bits;
}
if let Some(written) = self.written.get_mut(b).and_then(|words| words.get_mut(word)) {
*written = true;
}
}
pub fn buffers(&self) -> &[(u32, Vec<[u32; 4]>)] {
&self.buffers
}
pub fn unset_words(&self) -> u64 {
self.written.iter().flatten().filter(|w| !**w).count() as u64
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum Lane {
Bits(u32),
U,
V,
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum Input {
Lanes([Lane; 4]),
Constant([f32; 4]),
GridUv,
}
impl Input {
fn lanes(self) -> [Lane; 4] {
match self {
Self::Lanes(lanes) => lanes,
Self::Constant(c) => c.map(|v| Lane::Bits(v.to_bits())),
Self::GridUv => [Lane::U, Lane::V, Lane::Bits(0), Lane::Bits(0)],
}
}
}
#[derive(Clone, Copy, Debug)]
pub struct Grid {
pub size: u32,
pub period: [f32; 2],
}
#[derive(Debug)]
pub struct Evaluation {
pub targets: Vec<Option<Vec<[f32; 4]>>>,
pub instructions: u64,
pub samples: u64,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Invocation {
Scalar,
Quads,
}
#[derive(Default)]
struct Kept {
pixels: Vec<usize>,
discarded: Vec<bool>,
values: Vec<([u32; 4], u8)>,
instructions: u64,
samples: u64,
}
#[derive(Debug)]
pub struct Raw {
pub pixels: Vec<Vec<([u32; 4], u8)>>,
pub discarded: Vec<bool>,
pub instructions: u64,
pub samples: u64,
}
pub fn evaluate_raw(
shader: &dyn PixelShader,
constants: &Constants,
textures: &Views,
inputs: &BTreeMap<(String, u32), Input>,
grid: Grid,
targets: &[(u32, u8)],
invocation: Invocation,
) -> Result<Raw, Error> {
evaluate_raw_with(
Threads::ONE,
shader,
constants,
textures,
inputs,
grid,
targets,
invocation,
)
}
#[allow(clippy::too_many_arguments)]
pub fn evaluate_raw_with(
threads: Threads,
shader: &dyn PixelShader,
constants: &Constants,
textures: &Views,
inputs: &BTreeMap<(String, u32), Input>,
grid: Grid,
targets: &[(u32, u8)],
invocation: Invocation,
) -> Result<Raw, Error> {
if grid.size == 0 || grid.size > 4096 || !grid.period.iter().all(|p| p.is_finite() && *p > 0.) {
return Err(error(
0,
"grid size must be 1..=4096 and its period finite and positive",
));
}
if textures.absent == Absent::Refuse
&& let Some(slot) = shader.textures().iter().find(|s| !textures.views.contains_key(s))
{
return Err(error(0, format!("t{slot} is sampled but has no texture")));
}
if targets.iter().any(|&(_, mask)| mask == 0 || mask > 15) {
return Err(error(0, "a target's component mask must be 1..=15"));
}
let mut placed: Vec<(usize, usize, Lane)> = Vec::new();
let mut registers = vec![[0u32; 4]; shader.input_count()];
for element in shader.interface().inputs.iter().filter(|e| e.used) {
let (name, index) = (&element.label.name, element.label.index);
let key = inputs
.iter()
.find(|((n, i), _)| n.eq_ignore_ascii_case(name) && *i == index)
.map(|(_, input)| *input)
.ok_or_else(|| error(0, format!("no value for used input {name}{index}")))?;
let d3d = element
.provenance
.d3d
.ok_or_else(|| error(0, format!("input {name}{index} has no D3D register")))?;
let register = d3d.register as usize;
if register >= registers.len() || d3d.mask == 0 || d3d.mask > 15 {
return Err(error(
0,
format!("input {name}{index} is outside the program's registers"),
));
}
let components = (0..4).filter(|c| d3d.mask & (1 << c) != 0);
for (component, lane) in components.zip(key.lanes()) {
match lane {
Lane::Bits(bits) => {
if let Some(slot) = registers.get_mut(register).and_then(|r| r.get_mut(component)) {
*slot = bits;
}
}
_ => placed.push((register, component, lane)),
}
}
}
let mut derivatives = vec![[[0u32; 4]; 2]; shader.input_count()];
for &(register, component, lane) in &placed {
let (axis, step) = if lane == Lane::U {
(0, grid.period[0] / grid.size as f32)
} else {
(1, grid.period[1] / grid.size as f32)
};
if let Some(slot) = derivatives
.get_mut(register)
.and_then(|d| d.get_mut(axis))
.and_then(|d| d.get_mut(component))
{
*slot = step.to_bits();
}
}
let refs: Vec<(u32, &[[u32; 4]])> = constants
.buffers
.iter()
.map(|(s, v)| (*s, v.as_slice()))
.collect();
let size = grid.size as usize;
let place = |x: u32, y: u32, r: &mut [[u32; 4]]| {
let u = (x as f32 + 0.5) / grid.size as f32 * grid.period[0];
let v = (y as f32 + 0.5) / grid.size as f32 * grid.period[1];
r.copy_from_slice(®isters);
for &(register, lane, source) in &placed {
if let Some(slot) = r.get_mut(register).and_then(|r| r.get_mut(lane)) {
*slot = if source == Lane::U { u } else { v }.to_bits();
}
}
};
let at = |x: u32, y: u32| -> Vec<[u32; 4]> {
let mut r = registers.clone();
place(x, y, &mut r);
r
};
let row = |y: u32| -> Result<Kept, Error> {
let mut sampler = textures;
let mut out = Kept::default();
let mut keep = |pixel: usize, result: &crate::Execution| {
out.instructions = out.instructions.saturating_add(result.instructions as u64);
out.samples = out.samples.saturating_add(result.samples as u64);
out.pixels.push(pixel);
out.discarded.push(result.discarded);
for &(register, mask) in targets {
let written = result.output_masks.get(register as usize).copied().unwrap_or(0) & mask;
let bits = result.outputs.get(register as usize).copied().unwrap_or([0; 4]);
out.values.push((bits, written));
}
};
match invocation {
Invocation::Scalar => {
let width = registers.len();
let mut row_registers = vec![[0u32; 4]; width.saturating_mul(size)];
for (x, r) in (0..grid.size).zip(row_registers.chunks_exact_mut(width.max(1))) {
if let Some(r) = r.get_mut(..width) {
place(x, y, r);
}
}
let lanes: Vec<&[[u32; 4]]> = (0..size)
.map(|x| {
row_registers
.get(x.saturating_mul(width)..x.saturating_add(1).saturating_mul(width))
.unwrap_or_default()
})
.collect();
let first = y.saturating_mul(grid.size) as usize;
shader
.execute_lanes(&lanes, &derivatives, &refs, &mut sampler, &mut |x, result| {
keep(first.saturating_add(x), result)
})
.map_err(|(x, mut e)| {
e.detail = format!("pixel {}: {}", first.saturating_add(x), e.detail);
e
})?;
}
Invocation::Quads => {
for x in (0..grid.size).step_by(2) {
let (x1, y1) = (x.saturating_add(1), y.saturating_add(1));
let lanes = [(x, y), (x1, y), (x, y1), (x1, y1)];
let inputs = lanes.map(|(lx, ly)| at(lx, ly));
let results = shader
.execute_quad(
[&inputs[0], &inputs[1], &inputs[2], &inputs[3]],
&refs,
&mut sampler,
)
.map_err(|mut e| {
e.detail = format!("quad at pixel ({x}, {y}): {}", e.detail);
e
})?;
for ((lx, ly), result) in lanes.into_iter().zip(&results) {
if lx < grid.size && ly < grid.size {
keep(ly.saturating_mul(grid.size).saturating_add(lx) as usize, result);
}
}
}
}
}
Ok(out)
};
let rows: Vec<u32> = match invocation {
Invocation::Scalar => (0..grid.size).collect(),
Invocation::Quads => (0..grid.size).step_by(2).collect(),
};
let done = primase_foundations::parallel::map(threads, &rows, |_, &y| row(y)).map_err(|p| {
error(
0,
format!("row {}: {}", rows.get(p.item).copied().unwrap_or(0), p.message),
)
})?;
let mut pixels: Vec<Vec<([u32; 4], u8)>> = targets
.iter()
.map(|_| vec![([0; 4], 0); size.saturating_mul(size)])
.collect();
let mut discarded = vec![false; size.saturating_mul(size)];
let (mut instructions, mut samples) = (0u64, 0u64);
for r in done {
let kept = r?;
instructions = instructions.saturating_add(kept.instructions);
samples = samples.saturating_add(kept.samples);
for (k, &pixel) in kept.pixels.iter().enumerate() {
if let (Some(slot), Some(&was)) = (discarded.get_mut(pixel), kept.discarded.get(k)) {
*slot = was;
}
for (t, target) in pixels.iter_mut().enumerate() {
let value = kept.values.get(k.saturating_mul(targets.len()).saturating_add(t));
if let (Some(slot), Some(&value)) = (target.get_mut(pixel), value) {
*slot = value;
}
}
}
}
Ok(Raw {
pixels,
discarded,
instructions,
samples,
})
}
pub fn evaluate(
shader: &dyn PixelShader,
constants: &Constants,
textures: &Views,
inputs: &BTreeMap<(String, u32), Input>,
grid: Grid,
targets: &[(u32, u8)],
) -> Result<Evaluation, Error> {
evaluate_with(Threads::ONE, shader, constants, textures, inputs, grid, targets)
}
pub fn evaluate_with(
threads: Threads,
shader: &dyn PixelShader,
constants: &Constants,
textures: &Views,
inputs: &BTreeMap<(String, u32), Input>,
grid: Grid,
targets: &[(u32, u8)],
) -> Result<Evaluation, Error> {
let raw = evaluate_raw_with(
threads,
shader,
constants,
textures,
inputs,
grid,
targets,
Invocation::Scalar,
)?;
let mut out = Vec::with_capacity(targets.len());
for (t, &(register, mask)) in targets.iter().enumerate() {
let Some(pixels) = raw.pixels.get(t) else {
return Err(error(
0,
format!("target {t}, where the evaluation has {}", raw.pixels.len()),
));
};
let writes = pixels.first().is_some_and(|&(_, m)| m == mask);
if let Some(pixel) = pixels.iter().position(|&(_, m)| (m == mask) != writes) {
return Err(error(
pixel,
format!("o{register} is written at some pixels only"),
));
}
out.push(writes.then(|| {
pixels
.iter()
.map(|&(bits, _)| {
std::array::from_fn(|c| {
if mask & (1 << c) != 0 {
f32::from_bits(bits.get(c).copied().unwrap_or(0))
} else {
0.
}
})
})
.collect()
}));
}
Ok(Evaluation {
targets: out,
instructions: raw.instructions,
samples: raw.samples,
})
}