1use crate::{Absent, Error, PixelShader, Views};
6use iridia::{ConstantType, D3dClass, Interface, Member, ResourceKind};
7use std::collections::BTreeMap;
8
9use primase_foundations::parallel::Threads;
10
11fn error(at: usize, detail: impl Into<String>) -> Error {
12 Error {
13 bytecode: "evaluate",
14 at,
15 detail: detail.into(),
16 }
17}
18
19#[derive(Clone, Copy, Debug, PartialEq, Eq)]
21pub struct Field {
22 pub buffer: u32,
24 pub offset: u32,
26 pub words: u32,
28}
29
30#[derive(Clone, Debug, Default)]
33pub struct Layout {
34 pub buffers: Vec<(u32, u32)>,
36 pub fields: BTreeMap<String, Field>,
38}
39impl Layout {
40 pub fn of(shader: &dyn PixelShader) -> Result<Self, Error> {
44 let mut layout = Self {
45 buffers: shader
46 .constant_buffers()
47 .iter()
48 .map(|&(slot, rows)| {
49 rows.checked_mul(4)
50 .map(|words| (slot, words))
51 .ok_or_else(|| error(0, "constant buffer size overflow"))
52 })
53 .collect::<Result<_, _>>()?,
54 fields: BTreeMap::new(),
55 };
56 layout.add_named(shader.interface())?;
57 Ok(layout)
58 }
59 fn add_named(&mut self, interface: &Interface) -> Result<(), Error> {
60 for resource in &interface.resources {
61 let ResourceKind::ConstantBuffer { layout } = &resource.kind else {
62 continue;
63 };
64 let slot = resource
65 .provenance
66 .d3d
67 .filter(|b| b.class == D3dClass::ConstantBuffer)
68 .ok_or_else(|| {
69 error(
70 0,
71 format!(
72 "buffer {} has no constant-buffer register",
73 resource.provenance.name
74 ),
75 )
76 })?
77 .register;
78 for c in &layout.constants {
79 match c.ty.members() {
80 [] => self.insert(slot, &c.name, c.offset, words(&c.ty)?)?,
81 members => self.members(slot, c.offset, members)?,
82 }
83 }
84 }
85 Ok(())
86 }
87 fn members(&mut self, slot: u32, offset: u32, members: &[Member]) -> Result<(), Error> {
88 for m in members {
89 let at = offset
90 .checked_add(m.offset)
91 .ok_or_else(|| error(0, "member offset overflow"))?;
92 match m.ty.members() {
93 [] => self.insert(slot, &m.name, at, words(&m.ty)?)?,
94 inner => self.members(slot, at, inner)?,
95 }
96 }
97 Ok(())
98 }
99 fn insert(&mut self, buffer: u32, name: &str, offset: u32, words: u32) -> Result<(), Error> {
100 if !offset.is_multiple_of(4) {
101 return Err(error(
102 offset as usize,
103 format!("field {name} is not word-aligned"),
104 ));
105 }
106 let field = Field {
107 buffer,
108 offset: offset / 4,
109 words,
110 };
111 if self.fields.insert(name.to_owned(), field).is_some() {
112 return Err(error(offset as usize, format!("field name {name} occurs twice")));
113 }
114 Ok(())
115 }
116}
117
118fn words(ty: &ConstantType) -> Result<u32, Error> {
120 ty.size()
121 .map(|bytes| bytes.div_ceil(4))
122 .ok_or_else(|| error(0, "a constant's layout overflows"))
123}
124
125#[derive(Clone, Debug)]
127pub struct Constants {
128 fields: BTreeMap<String, Field>,
129 buffers: Vec<(u32, Vec<[u32; 4]>)>,
130 written: Vec<Vec<bool>>,
131}
132impl Constants {
133 pub fn new(layout: &Layout) -> Self {
135 Self {
136 fields: layout.fields.clone(),
137 buffers: layout
138 .buffers
139 .iter()
140 .map(|&(slot, words)| (slot, vec![[0; 4]; words.div_ceil(4) as usize]))
141 .collect(),
142 written: layout
143 .buffers
144 .iter()
145 .map(|&(_, words)| vec![false; words.div_ceil(4) as usize * 4])
146 .collect(),
147 }
148 }
149 pub fn set(&mut self, field: &str, values: &[f32]) -> Result<bool, Error> {
153 let Some(&f) = self.fields.get(field) else {
154 return Ok(false);
155 };
156 if values.len() > f.words as usize {
157 return Err(error(
158 0,
159 format!("{} values for {field}, which has {} words", values.len(), f.words),
160 ));
161 }
162 if let Some(b) = self.buffers.iter().position(|(slot, _)| *slot == f.buffer) {
163 let words = self.written.get(b).map_or(0, Vec::len);
164 for (i, v) in values.iter().enumerate() {
165 let word = (f.offset as usize).saturating_add(i);
166 if word < words {
167 self.store(b, word, v.to_bits());
168 }
169 }
170 }
171 Ok(true)
172 }
173 pub fn set_words(&mut self, buffer: u32, word_offset: u32, words: &[u32]) -> Result<(), Error> {
176 let b = self
177 .buffers
178 .iter()
179 .position(|(slot, _)| *slot == buffer)
180 .ok_or_else(|| error(0, format!("the program reads no cb{buffer}")))?;
181 let end = (word_offset as usize)
182 .checked_add(words.len())
183 .filter(|&end| end <= self.written.get(b).map_or(0, Vec::len))
184 .ok_or_else(|| error(word_offset as usize, format!("words past the end of cb{buffer}")))?;
185 for (word, &bits) in (word_offset as usize..end).zip(words) {
186 self.store(b, word, bits);
187 }
188 Ok(())
189 }
190 fn store(&mut self, b: usize, word: usize, bits: u32) {
191 if let Some(slot) = self
192 .buffers
193 .get_mut(b)
194 .and_then(|(_, rows)| rows.get_mut(word / 4))
195 .and_then(|row| row.get_mut(word % 4))
196 {
197 *slot = bits;
198 }
199 if let Some(written) = self.written.get_mut(b).and_then(|words| words.get_mut(word)) {
200 *written = true;
201 }
202 }
203 pub fn buffers(&self) -> &[(u32, Vec<[u32; 4]>)] {
205 &self.buffers
206 }
207 pub fn unset_words(&self) -> u64 {
209 self.written.iter().flatten().filter(|w| !**w).count() as u64
210 }
211}
212
213#[derive(Clone, Copy, Debug, PartialEq)]
215pub enum Lane {
216 Bits(u32),
218 U,
220 V,
222}
223#[derive(Clone, Copy, Debug, PartialEq)]
226pub enum Input {
227 Lanes([Lane; 4]),
229 Constant([f32; 4]),
231 GridUv,
233}
234impl Input {
235 fn lanes(self) -> [Lane; 4] {
236 match self {
237 Self::Lanes(lanes) => lanes,
238 Self::Constant(c) => c.map(|v| Lane::Bits(v.to_bits())),
239 Self::GridUv => [Lane::U, Lane::V, Lane::Bits(0), Lane::Bits(0)],
240 }
241 }
242}
243
244#[derive(Clone, Copy, Debug)]
248pub struct Grid {
249 pub size: u32,
251 pub period: [f32; 2],
253}
254
255#[derive(Debug)]
257pub struct Evaluation {
258 pub targets: Vec<Option<Vec<[f32; 4]>>>,
261 pub instructions: u64,
263 pub samples: u64,
265}
266
267#[derive(Clone, Copy, Debug, PartialEq, Eq)]
269pub enum Invocation {
270 Scalar,
273 Quads,
278}
279
280#[derive(Default)]
283struct Kept {
284 pixels: Vec<usize>,
285 discarded: Vec<bool>,
286 values: Vec<([u32; 4], u8)>,
287 instructions: u64,
288 samples: u64,
289}
290
291#[derive(Debug)]
293pub struct Raw {
294 pub pixels: Vec<Vec<([u32; 4], u8)>>,
297 pub discarded: Vec<bool>,
299 pub instructions: u64,
301 pub samples: u64,
303}
304
305pub fn evaluate_raw(
311 shader: &dyn PixelShader,
312 constants: &Constants,
313 textures: &Views,
314 inputs: &BTreeMap<(String, u32), Input>,
315 grid: Grid,
316 targets: &[(u32, u8)],
317 invocation: Invocation,
318) -> Result<Raw, Error> {
319 evaluate_raw_with(
320 Threads::ONE,
321 shader,
322 constants,
323 textures,
324 inputs,
325 grid,
326 targets,
327 invocation,
328 )
329}
330
331#[allow(clippy::too_many_arguments)]
335pub fn evaluate_raw_with(
336 threads: Threads,
337 shader: &dyn PixelShader,
338 constants: &Constants,
339 textures: &Views,
340 inputs: &BTreeMap<(String, u32), Input>,
341 grid: Grid,
342 targets: &[(u32, u8)],
343 invocation: Invocation,
344) -> Result<Raw, Error> {
345 if grid.size == 0 || grid.size > 4096 || !grid.period.iter().all(|p| p.is_finite() && *p > 0.) {
346 return Err(error(
347 0,
348 "grid size must be 1..=4096 and its period finite and positive",
349 ));
350 }
351 if textures.absent == Absent::Refuse
353 && let Some(slot) = shader.textures().iter().find(|s| !textures.views.contains_key(s))
354 {
355 return Err(error(0, format!("t{slot} is sampled but has no texture")));
356 }
357 if targets.iter().any(|&(_, mask)| mask == 0 || mask > 15) {
358 return Err(error(0, "a target's component mask must be 1..=15"));
359 }
360 let mut placed: Vec<(usize, usize, Lane)> = Vec::new();
362 let mut registers = vec![[0u32; 4]; shader.input_count()];
363 for element in shader.interface().inputs.iter().filter(|e| e.used) {
364 let (name, index) = (&element.label.name, element.label.index);
365 let key = inputs
366 .iter()
367 .find(|((n, i), _)| n.eq_ignore_ascii_case(name) && *i == index)
368 .map(|(_, input)| *input)
369 .ok_or_else(|| error(0, format!("no value for used input {name}{index}")))?;
370 let d3d = element
371 .provenance
372 .d3d
373 .ok_or_else(|| error(0, format!("input {name}{index} has no D3D register")))?;
374 let register = d3d.register as usize;
375 if register >= registers.len() || d3d.mask == 0 || d3d.mask > 15 {
376 return Err(error(
377 0,
378 format!("input {name}{index} is outside the program's registers"),
379 ));
380 }
381 let components = (0..4).filter(|c| d3d.mask & (1 << c) != 0);
382 for (component, lane) in components.zip(key.lanes()) {
383 match lane {
384 Lane::Bits(bits) => {
385 if let Some(slot) = registers.get_mut(register).and_then(|r| r.get_mut(component)) {
386 *slot = bits;
387 }
388 }
389 _ => placed.push((register, component, lane)),
390 }
391 }
392 }
393 let mut derivatives = vec![[[0u32; 4]; 2]; shader.input_count()];
395 for &(register, component, lane) in &placed {
396 let (axis, step) = if lane == Lane::U {
397 (0, grid.period[0] / grid.size as f32)
398 } else {
399 (1, grid.period[1] / grid.size as f32)
400 };
401 if let Some(slot) = derivatives
402 .get_mut(register)
403 .and_then(|d| d.get_mut(axis))
404 .and_then(|d| d.get_mut(component))
405 {
406 *slot = step.to_bits();
407 }
408 }
409 let refs: Vec<(u32, &[[u32; 4]])> = constants
410 .buffers
411 .iter()
412 .map(|(s, v)| (*s, v.as_slice()))
413 .collect();
414 let size = grid.size as usize;
415 let place = |x: u32, y: u32, r: &mut [[u32; 4]]| {
417 let u = (x as f32 + 0.5) / grid.size as f32 * grid.period[0];
418 let v = (y as f32 + 0.5) / grid.size as f32 * grid.period[1];
419 r.copy_from_slice(®isters);
420 for &(register, lane, source) in &placed {
421 if let Some(slot) = r.get_mut(register).and_then(|r| r.get_mut(lane)) {
422 *slot = if source == Lane::U { u } else { v }.to_bits();
423 }
424 }
425 };
426 let at = |x: u32, y: u32| -> Vec<[u32; 4]> {
427 let mut r = registers.clone();
428 place(x, y, &mut r);
429 r
430 };
431 let row = |y: u32| -> Result<Kept, Error> {
432 let mut sampler = textures;
433 let mut out = Kept::default();
434 let mut keep = |pixel: usize, result: &crate::Execution| {
435 out.instructions = out.instructions.saturating_add(result.instructions as u64);
436 out.samples = out.samples.saturating_add(result.samples as u64);
437 out.pixels.push(pixel);
438 out.discarded.push(result.discarded);
439 for &(register, mask) in targets {
440 let written = result.output_masks.get(register as usize).copied().unwrap_or(0) & mask;
441 let bits = result.outputs.get(register as usize).copied().unwrap_or([0; 4]);
442 out.values.push((bits, written));
443 }
444 };
445 match invocation {
446 Invocation::Scalar => {
447 let width = registers.len();
449 let mut row_registers = vec![[0u32; 4]; width.saturating_mul(size)];
450 for (x, r) in (0..grid.size).zip(row_registers.chunks_exact_mut(width.max(1))) {
451 if let Some(r) = r.get_mut(..width) {
452 place(x, y, r);
453 }
454 }
455 let lanes: Vec<&[[u32; 4]]> = (0..size)
456 .map(|x| {
457 row_registers
458 .get(x.saturating_mul(width)..x.saturating_add(1).saturating_mul(width))
459 .unwrap_or_default()
460 })
461 .collect();
462 let first = y.saturating_mul(grid.size) as usize;
463 shader
464 .execute_lanes(&lanes, &derivatives, &refs, &mut sampler, &mut |x, result| {
465 keep(first.saturating_add(x), result)
466 })
467 .map_err(|(x, mut e)| {
468 e.detail = format!("pixel {}: {}", first.saturating_add(x), e.detail);
469 e
470 })?;
471 }
472 Invocation::Quads => {
473 for x in (0..grid.size).step_by(2) {
474 let (x1, y1) = (x.saturating_add(1), y.saturating_add(1));
475 let lanes = [(x, y), (x1, y), (x, y1), (x1, y1)];
476 let inputs = lanes.map(|(lx, ly)| at(lx, ly));
477 let results = shader
478 .execute_quad(
479 [&inputs[0], &inputs[1], &inputs[2], &inputs[3]],
480 &refs,
481 &mut sampler,
482 )
483 .map_err(|mut e| {
484 e.detail = format!("quad at pixel ({x}, {y}): {}", e.detail);
485 e
486 })?;
487 for ((lx, ly), result) in lanes.into_iter().zip(&results) {
488 if lx < grid.size && ly < grid.size {
489 keep(ly.saturating_mul(grid.size).saturating_add(lx) as usize, result);
490 }
491 }
492 }
493 }
494 }
495 Ok(out)
496 };
497 let rows: Vec<u32> = match invocation {
498 Invocation::Scalar => (0..grid.size).collect(),
499 Invocation::Quads => (0..grid.size).step_by(2).collect(),
500 };
501 let done = primase_foundations::parallel::map(threads, &rows, |_, &y| row(y)).map_err(|p| {
502 error(
503 0,
504 format!("row {}: {}", rows.get(p.item).copied().unwrap_or(0), p.message),
505 )
506 })?;
507 let mut pixels: Vec<Vec<([u32; 4], u8)>> = targets
508 .iter()
509 .map(|_| vec![([0; 4], 0); size.saturating_mul(size)])
510 .collect();
511 let mut discarded = vec![false; size.saturating_mul(size)];
512 let (mut instructions, mut samples) = (0u64, 0u64);
513 for r in done {
514 let kept = r?;
515 instructions = instructions.saturating_add(kept.instructions);
516 samples = samples.saturating_add(kept.samples);
517 for (k, &pixel) in kept.pixels.iter().enumerate() {
518 if let (Some(slot), Some(&was)) = (discarded.get_mut(pixel), kept.discarded.get(k)) {
519 *slot = was;
520 }
521 for (t, target) in pixels.iter_mut().enumerate() {
522 let value = kept.values.get(k.saturating_mul(targets.len()).saturating_add(t));
523 if let (Some(slot), Some(&value)) = (target.get_mut(pixel), value) {
524 *slot = value;
525 }
526 }
527 }
528 }
529 Ok(Raw {
530 pixels,
531 discarded,
532 instructions,
533 samples,
534 })
535}
536
537pub fn evaluate(
542 shader: &dyn PixelShader,
543 constants: &Constants,
544 textures: &Views,
545 inputs: &BTreeMap<(String, u32), Input>,
546 grid: Grid,
547 targets: &[(u32, u8)],
548) -> Result<Evaluation, Error> {
549 evaluate_with(Threads::ONE, shader, constants, textures, inputs, grid, targets)
550}
551
552pub fn evaluate_with(
555 threads: Threads,
556 shader: &dyn PixelShader,
557 constants: &Constants,
558 textures: &Views,
559 inputs: &BTreeMap<(String, u32), Input>,
560 grid: Grid,
561 targets: &[(u32, u8)],
562) -> Result<Evaluation, Error> {
563 let raw = evaluate_raw_with(
564 threads,
565 shader,
566 constants,
567 textures,
568 inputs,
569 grid,
570 targets,
571 Invocation::Scalar,
572 )?;
573 let mut out = Vec::with_capacity(targets.len());
574 for (t, &(register, mask)) in targets.iter().enumerate() {
575 let Some(pixels) = raw.pixels.get(t) else {
576 return Err(error(
577 0,
578 format!("target {t}, where the evaluation has {}", raw.pixels.len()),
579 ));
580 };
581 let writes = pixels.first().is_some_and(|&(_, m)| m == mask);
582 if let Some(pixel) = pixels.iter().position(|&(_, m)| (m == mask) != writes) {
583 return Err(error(
584 pixel,
585 format!("o{register} is written at some pixels only"),
586 ));
587 }
588 out.push(writes.then(|| {
589 pixels
590 .iter()
591 .map(|&(bits, _)| {
592 std::array::from_fn(|c| {
593 if mask & (1 << c) != 0 {
594 f32::from_bits(bits.get(c).copied().unwrap_or(0))
595 } else {
596 0.
597 }
598 })
599 })
600 .collect()
601 }));
602 }
603 Ok(Evaluation {
604 targets: out,
605 instructions: raw.instructions,
606 samples: raw.samples,
607 })
608}