Skip to main content

iridia_exec/
pixel.rs

1//! A [`Program`] lifted from D3D bytecode behind [`PixelShader`]: inputs and outputs by signature register, constant
2//! buffers and samplers by bind slot, as a caller that evaluates a shader over many pixels drives it. A vertex program has the same register view
3//! (its inputs the vertex attributes by register, its outputs the varyings by register; the position and clip distances,
4//! which have no output register, are not in it), so a caller can run one per vertex through [`PixelShader::execute`].
5
6use std::collections::BTreeMap;
7
8use super::{
9    Backend, Element, Executable, ImageAtomic, ImageLoad, ImageQuery, ImageSample, ImageStore, Runner,
10    Scratch, error,
11};
12use crate::{AtomicAdd, Error, Execution, PixelShader, Sampler, Store};
13use iridia::{
14    BufferKind, D3dClass, Interface, Program, ResourceId, ResourceKind, Stage, StorageTarget, SystemValue,
15    TextureDim,
16};
17use std::sync::Mutex;
18
19/// One entry argument and where its lanes come from in the register file.
20#[derive(Debug)]
21struct Argument {
22    register: usize,
23    /// The register lanes it takes, in order.
24    lanes: Vec<u8>,
25    /// A front-facing flag: the register word is non-zero for a front face.
26    boolean: bool,
27    used: bool,
28}
29
30/// One member of the result and the register lanes it is written to.
31#[derive(Debug)]
32struct Member {
33    register: usize,
34    lanes: Vec<u8>,
35    /// The lanes the program writes, as its signature states.
36    written: u8,
37}
38
39/// A pixel program with the register view the D3D readers' callers use.
40#[derive(Debug)]
41pub struct ProgramShader {
42    program: Program,
43    executable: Executable,
44    arguments: Vec<Argument>,
45    members: Vec<Member>,
46    input_count: usize,
47    output_count: usize,
48    /// A vertex program's result member that is its clip-space position (`SV_Position`), which has no place in the
49    /// register view: [`ProgramShader::execute_vertex`] returns it apart.
50    position: Option<usize>,
51    /// Each constant buffer: bind slot, resource, rows.
52    constants: Vec<(u32, ResourceId, u32)>,
53    constant_buffers: Vec<(u32, u32)>,
54    textures: Vec<u32>,
55    samplers: Vec<u32>,
56    /// The bind slot of every resource that has one.
57    slots: BTreeMap<ResourceId, u32>,
58    buffers: BTreeMap<ResourceId, BufferKind>,
59    /// Registers kept between runs, one set per concurrent run.
60    scalar_scratch: Mutex<Vec<Scratch<1>>>,
61    quad_scratch: Mutex<Vec<Scratch<4>>>,
62    lane_scratch: Mutex<Vec<Scratch<LANES>>>,
63}
64
65/// The invocations [`PixelShader::execute_lanes`] runs in lockstep at a time.
66const LANES: usize = 16;
67
68/// The buffers lockstep runs reuse: every invocation's entry arguments, and the execution given to the caller.
69#[derive(Default)]
70struct Room {
71    arguments: Vec<[u32; 4]>,
72    execution: Execution,
73}
74
75fn lanes_of(mask: u8) -> Vec<u8> {
76    (0..4).filter(|l| mask >> l & 1 != 0).collect()
77}
78
79/// The lanes the module carries for an input: the pipeline fixes some, the interface states the rest.
80fn width_of(v: &iridia::Varying) -> usize {
81    match v.system {
82        Some(SystemValue::FragCoord) => 4,
83        Some(
84            SystemValue::FrontFacing
85            | SystemValue::VertexIndex
86            | SystemValue::InstanceIndex
87            | SystemValue::PrimitiveIndex
88            | SystemValue::SampleIndex
89            | SystemValue::Coverage,
90        ) => 1,
91        _ => v.value.width as usize,
92    }
93}
94
95impl ProgramShader {
96    /// Compile `program`. Its inputs and outputs need their D3D registers (the lifter keeps them in provenance),
97    /// and every resource a D3D bind slot.
98    pub fn new(program: Program) -> Result<Self, Error> {
99        if !matches!(program.stage, Stage::Pixel | Stage::Vertex) {
100            return Err(error("not a pixel or vertex program"));
101        }
102        let executable = Executable::new(&program)?;
103        let interface = &program.interface;
104        let mut arguments = Vec::new();
105        let mut input_count = 0;
106        // the module's arguments: the interface's inputs the entry point carries, in order
107        let io = iridia::entry_io(interface).map_err(error)?;
108        for item in &io.arguments {
109            let v = item
110                .varyings
111                .first()
112                .and_then(|&i| interface.inputs.get(i))
113                .ok_or_else(|| error("an entry argument names no input varying"))?;
114            let name = Program::varying_name(&v.label);
115            // the primitive and coverage inputs have no register in the signature: the register view cannot carry
116            // them, they read as 0
117            if v.provenance.d3d.is_none()
118                && matches!(
119                    v.system,
120                    Some(SystemValue::PrimitiveIndex | SystemValue::Coverage | SystemValue::SampleIndex)
121                )
122            {
123                arguments.push(Argument {
124                    register: usize::MAX,
125                    lanes: Vec::new(),
126                    boolean: false,
127                    used: false,
128                });
129                continue;
130            }
131            let sig = v
132                .provenance
133                .d3d
134                .ok_or_else(|| error(format!("input {name} has no D3D register")))?;
135            let lanes = lanes_of(sig.mask);
136            let width = width_of(v);
137            if lanes.len() < width {
138                return Err(error(format!(
139                    "input {name} has {width} lanes but its register mask {:#x} marks {}",
140                    sig.mask,
141                    lanes.len()
142                )));
143            }
144            if v.used {
145                input_count = input_count.max(sig.register as usize + 1);
146            }
147            arguments.push(Argument {
148                register: sig.register as usize,
149                lanes: lanes.iter().take(width).copied().collect(),
150                boolean: v.system == Some(SystemValue::FrontFacing),
151                used: v.used,
152            });
153        }
154        let mut members = Vec::new();
155        let mut output_count = 0;
156        let position = interface
157            .outputs
158            .iter()
159            .position(|v| program.stage == Stage::Vertex && v.system == Some(SystemValue::ClipPosition));
160        for v in &interface.outputs {
161            let name = Program::varying_name(&v.label);
162            // depth, coverage and stencil have no output register
163            if !matches!(v.system, None | Some(SystemValue::Target(_))) {
164                members.push(Member {
165                    register: usize::MAX,
166                    lanes: Vec::new(),
167                    written: 0,
168                });
169                continue;
170            }
171            let sig = v
172                .provenance
173                .d3d
174                .ok_or_else(|| error(format!("output {name} has no D3D register")))?;
175            output_count = output_count.max(sig.register as usize + 1);
176            members.push(Member {
177                register: sig.register as usize,
178                lanes: lanes_of(sig.mask),
179                // an element the program does not write (the signature lists it, `used` is false) has no lane written
180                written: if v.used {
181                    sig.mask & !sig.read_write_mask
182                } else {
183                    0
184                },
185            });
186        }
187        let mut constants = Vec::new();
188        let (mut textures, mut samplers) = (Vec::new(), Vec::new());
189        // the textures and samplers the compiled code reaches, not every one the program declares
190        let (sampled_textures, sampled_samplers) = executable.sampled();
191        let mut slots = BTreeMap::new();
192        let mut buffers = BTreeMap::new();
193        for r in &interface.resources {
194            let binding = r
195                .provenance
196                .d3d
197                .ok_or_else(|| error(format!("resource {} has no D3D bind slot", r.provenance.name)))?;
198            slots.insert(r.id, binding.register);
199            match &r.kind {
200                ResourceKind::ConstantBuffer { .. } => {
201                    let rows = rows_of(&program, r.id).ok_or_else(|| {
202                        error(format!(
203                            "constant buffer {} has no module array",
204                            r.provenance.name
205                        ))
206                    })?;
207                    constants.push((binding.register, r.id, rows));
208                }
209                // the 2D textures (and arrays of them) the code reaches: what `Sampler::sample` answers, and what the
210                // evaluators' `textures()` always listed; a volume or a cube is reached through `sample_image`
211                ResourceKind::Texture {
212                    dim: TextureDim::D2, ..
213                } if binding.class == D3dClass::ShaderResource && sampled_textures.contains(&r.id) => {
214                    textures.push(binding.register);
215                }
216                ResourceKind::Sampler { .. } if sampled_samplers.contains(&r.id) => {
217                    samplers.push(binding.register);
218                }
219                ResourceKind::Buffer(kind)
220                | ResourceKind::Storage {
221                    target: StorageTarget::Buffer(kind),
222                    ..
223                } => {
224                    buffers.insert(r.id, *kind);
225                }
226                _ => {}
227            }
228        }
229        textures.sort_unstable();
230        textures.dedup();
231        samplers.sort_unstable();
232        samplers.dedup();
233        let constant_buffers = constants.iter().map(|&(slot, _, rows)| (slot, rows)).collect();
234        Ok(Self {
235            program,
236            executable,
237            arguments,
238            members,
239            input_count,
240            output_count,
241            position,
242            constants,
243            constant_buffers,
244            textures,
245            samplers,
246            slots,
247            buffers,
248            scalar_scratch: Mutex::new(Vec::new()),
249            quad_scratch: Mutex::new(Vec::new()),
250            lane_scratch: Mutex::new(Vec::new()),
251        })
252    }
253
254    /// The vertex or pixel program the shader runs.
255    pub fn program(&self) -> &Program {
256        &self.program
257    }
258
259    /// Whether a caller that wants this program's exact results must run it as a quad ([`Executable::needs_quad`]).
260    pub fn needs_quad(&self) -> bool {
261        self.executable.needs_quad()
262    }
263
264    /// The entry arguments for one invocation's input registers.
265    fn arguments_of(&self, inputs: &[[u32; 4]]) -> Result<Vec<[u32; 4]>, Error> {
266        let mut out = Vec::with_capacity(self.arguments.len());
267        self.arguments_into(inputs, &mut out)?;
268        Ok(out)
269    }
270
271    /// [`ProgramShader::arguments_of`] appended to `out`.
272    fn arguments_into(&self, inputs: &[[u32; 4]], out: &mut Vec<[u32; 4]>) -> Result<(), Error> {
273        if inputs.len() < self.input_count {
274            return Err(error(format!(
275                "{} input registers given, {} needed",
276                inputs.len(),
277                self.input_count
278            )));
279        }
280        for a in &self.arguments {
281            let mut value = [0u32; 4];
282            if let Some(register) = inputs.get(a.register) {
283                for (slot, lane) in value.iter_mut().zip(&a.lanes) {
284                    *slot = register.get(*lane as usize).copied().unwrap_or(0);
285                }
286            } else if a.used {
287                return Err(error(format!("input register {} not given", a.register)));
288            }
289            if a.boolean {
290                value[0] = u32::from(value[0] != 0);
291            }
292            out.push(value);
293        }
294        Ok(())
295    }
296
297    fn derivatives_of(&self, derivatives: &[[[u32; 4]; 2]]) -> Vec<[[u32; 4]; 2]> {
298        self.arguments
299            .iter()
300            .map(|a| {
301                let mut value = [[0u32; 4]; 2];
302                if let Some(d) = derivatives.get(a.register) {
303                    for (out, from) in value.iter_mut().zip(d) {
304                        for (slot, lane) in out.iter_mut().zip(&a.lanes) {
305                            *slot = from.get(*lane as usize).copied().unwrap_or(0);
306                        }
307                    }
308                }
309                value
310            })
311            .collect()
312    }
313
314    fn execution(&self, o: super::Output) -> Execution {
315        let mut e = Execution::default();
316        self.execution_into(&o.values, o.discarded, o.steps, o.samples, &mut e);
317        e
318    }
319
320    /// What a run's result members `values` are in the register view, written over `e`.
321    fn execution_into(
322        &self,
323        values: &[[u32; 4]],
324        discarded: bool,
325        steps: u64,
326        samples: u64,
327        e: &mut Execution,
328    ) {
329        e.outputs.clear();
330        e.outputs.resize(self.output_count, [0; 4]);
331        e.output_masks.clear();
332        e.output_masks.resize(self.output_count, 0);
333        for (m, value) in self.members.iter().zip(values) {
334            if m.register == usize::MAX {
335                continue;
336            }
337            if let Some(register) = e.outputs.get_mut(m.register) {
338                for (lane, word) in m.lanes.iter().zip(value) {
339                    if let Some(slot) = register.get_mut(*lane as usize) {
340                        *slot = *word;
341                    }
342                }
343            }
344            if let Some(mask) = e.output_masks.get_mut(m.register) {
345                *mask |= m.written;
346            }
347        }
348        e.instructions = usize::try_from(steps).unwrap_or(usize::MAX);
349        e.samples = usize::try_from(samples).unwrap_or(usize::MAX);
350        e.discarded = discarded;
351    }
352
353    fn bound<'a>(&self, buffers: &[(u32, &'a [[u32; 4]])]) -> Result<super::Bound<'a>, Error> {
354        for &(slot, _, rows) in &self.constants {
355            let given = buffers
356                .iter()
357                .find(|(s, _)| *s == slot)
358                .map(|(_, v)| *v)
359                .ok_or_else(|| error(format!("no constant buffer cb{slot}")))?;
360            if given.len() < rows as usize {
361                return Err(error(format!(
362                    "cb{slot}: {} rows given, {rows} needed",
363                    given.len()
364                )));
365            }
366        }
367        // the buffers were all found above, by slot
368        self.executable
369            .bind(
370                |id| {
371                    let (slot, _, _) = self.constants.iter().find(|(_, c, _)| *c == id)?;
372                    buffers.iter().find(|(s, _)| s == slot).map(|(_, v)| *v)
373                },
374                |_| None,
375            )
376            .map_err(error)
377    }
378
379    fn backend<'a>(&'a self, sampler: &'a mut dyn Sampler) -> SlotBackend<'a> {
380        SlotBackend {
381            sampler,
382            slots: &self.slots,
383            buffers: &self.buffers,
384        }
385    }
386}
387
388/// A set of registers from the pool, if one is free.
389fn take<const N: usize>(pool: &Mutex<Vec<Scratch<N>>>) -> Option<Scratch<N>> {
390    pool.lock().ok()?.pop()
391}
392
393fn give_back<const N: usize>(pool: &Mutex<Vec<Scratch<N>>>, scratch: Scratch<N>) {
394    if let Ok(mut pool) = pool.lock() {
395        pool.push(scratch);
396    }
397}
398
399/// The rows of a constant buffer's array in the module.
400fn rows_of(program: &Program, id: ResourceId) -> Option<u32> {
401    let global = program
402        .module
403        .global_variables
404        .try_get(program.global(id)?)
405        .ok()?;
406    match &program.module.types[global.ty].inner {
407        naga::TypeInner::Array {
408            size: naga::ArraySize::Constant(n),
409            ..
410        } => Some(n.get()),
411        _ => None,
412    }
413}
414
415/// The program's requests in the sampler's vocabulary: bind slots instead of resource ids, and a buffer's words as the
416/// element and byte offset its view addresses.
417pub(super) struct SlotBackend<'a> {
418    pub(super) sampler: &'a mut dyn Sampler,
419    pub(super) slots: &'a BTreeMap<ResourceId, u32>,
420    pub(super) buffers: &'a BTreeMap<ResourceId, BufferKind>,
421}
422impl SlotBackend<'_> {
423    fn slot(&self, id: ResourceId) -> Result<u32, String> {
424        self.slots
425            .get(&id)
426            .copied()
427            .ok_or_else(|| format!("resource {} has no bind slot", id.0))
428    }
429    /// The bind slot of an element: a slot answers one binding, not an array's.
430    fn slot_of(&self, e: Element) -> Result<u32, String> {
431        if e.index != 0 {
432            return Err(format!(
433                "resource {}: element {} of an array, which a bind-slot sampler does not answer",
434                e.id.0, e.index
435            ));
436        }
437        self.slot(e.id)
438    }
439    /// A buffer view's address of word `word`: (element, byte offset in the element).
440    fn address(&self, id: ResourceId, word: u32) -> [u32; 3] {
441        match self.buffers.get(&id) {
442            Some(BufferKind::Structured { stride }) if *stride >= 4 => {
443                let per = stride / 4;
444                [
445                    word.checked_div(per).unwrap_or(0),
446                    word.checked_rem(per).unwrap_or(0).saturating_mul(4),
447                    0,
448                ]
449            }
450            // raw and typed views are addressed by the word
451            _ => [word, 0, 0],
452        }
453    }
454}
455impl Backend for SlotBackend<'_> {
456    fn sample(&mut self, r: ImageSample<Element>) -> Result<[f32; 4], String> {
457        let (texture, sampler) = (self.slot_of(r.texture)?, self.slot_of(r.sampler)?);
458        self.sampler.sample_image(ImageSample {
459            texture,
460            sampler,
461            dim: r.dim,
462            coord: r.coord,
463            layer: r.layer,
464            lod: r.lod,
465            offset: r.offset,
466            gather: r.gather,
467            depth_ref: r.depth_ref,
468            depth: r.depth,
469        })
470    }
471    fn load(&mut self, r: ImageLoad<Element>) -> Result<[u32; 4], String> {
472        let texture = self.slot_of(r.texture)?;
473        self.sampler.load_image(ImageLoad {
474            texture,
475            dim: r.dim,
476            storage: r.storage,
477            texel: r.texel,
478            layer: r.layer,
479            sample: r.sample,
480            mip: r.mip,
481        })
482    }
483    fn query(&mut self, r: ImageQuery<Element>) -> Result<[u32; 4], String> {
484        let texture = self.slot_of(r.texture)?;
485        self.sampler.query_image(ImageQuery {
486            texture,
487            dim: r.dim,
488            kind: r.kind,
489            level: r.level,
490        })
491    }
492    fn store_image(&mut self, r: ImageStore<ResourceId>) -> Result<(), String> {
493        let view = self.slot_of(Element {
494            id: r.view,
495            index: r.index,
496        })?;
497        self.sampler.store_image(ImageStore {
498            view,
499            index: 0,
500            dim: r.dim,
501            texel: r.texel,
502            layer: r.layer,
503            value: r.value,
504            lanes: r.lanes,
505        })
506    }
507    fn atomic_image(&mut self, r: ImageAtomic<ResourceId>) -> Result<u32, String> {
508        let view = self.slot_of(Element {
509            id: r.view,
510            index: r.index,
511        })?;
512        self.sampler.atomic_image(ImageAtomic {
513            view,
514            index: 0,
515            dim: r.dim,
516            texel: r.texel,
517            layer: r.layer,
518            op: r.op,
519            signed: r.signed,
520            value: r.value,
521        })
522    }
523    fn store_buffer(&mut self, view: ResourceId, word: u32, value: [u32; 4], mask: u8) -> Result<(), String> {
524        let address = self.address(view, word);
525        let view = self.slot(view)?;
526        self.sampler.store(Store {
527            view,
528            address,
529            value,
530            mask,
531        })
532    }
533    fn atomic_add(&mut self, view: ResourceId, word: u32, value: u32) -> Result<u32, String> {
534        let address = self.address(view, word);
535        let view = self.slot(view)?;
536        self.sampler.atomic_add(AtomicAdd { view, address, value })
537    }
538    fn read_buffer(&mut self, view: ResourceId, word: u32, count: u8) -> Result<[u32; 4], String> {
539        let slot = self.slot(view)?;
540        let mut out = [0u32; 4];
541        match self.buffers.get(&view) {
542            // a typed view's element is four lanes, one load answers all of them
543            Some(BufferKind::Typed(_)) => {
544                let (mut at, end) = (word, word.saturating_add(u32::from(count)));
545                while at < end {
546                    let element = at / 4;
547                    let lanes = self.sampler.load(crate::Load {
548                        texture: slot,
549                        texel: [i32::try_from(element).unwrap_or(i32::MAX), 0, 0],
550                        mip: 0,
551                    })?;
552                    while at < end && at / 4 == element {
553                        let from = lanes.get((at % 4) as usize).copied().unwrap_or(0);
554                        if let Some(slot) = out.get_mut(at.saturating_sub(word) as usize) {
555                            *slot = from;
556                        }
557                        at = at.saturating_add(1);
558                    }
559                }
560            }
561            _ => {
562                for (i, w) in out.iter_mut().take(usize::from(count)).enumerate() {
563                    let [element, offset, _] =
564                        self.address(view, word.saturating_add(u32::try_from(i).unwrap_or(0)));
565                    *w = self.sampler.structured(slot, element, offset)?;
566                }
567            }
568        }
569        Ok(out)
570    }
571    fn buffer_words(&mut self, view: ResourceId) -> Result<u32, String> {
572        let slot = self.slot(view)?;
573        // without the sampler saying how long the buffer is, no read is out of range: its own error stops it
574        let Some(elements) = self.sampler.buffer_elements(slot) else {
575            return Ok(u32::MAX);
576        };
577        let per = match self.buffers.get(&view) {
578            Some(BufferKind::Typed(_)) => 4,
579            Some(BufferKind::Structured { stride }) => (stride / 4).max(1),
580            _ => 1,
581        };
582        Ok(elements.saturating_mul(per))
583    }
584}
585
586impl PixelShader for ProgramShader {
587    fn interface(&self) -> &Interface {
588        &self.program.interface
589    }
590    fn input_count(&self) -> usize {
591        self.input_count
592    }
593    fn constant_buffers(&self) -> &[(u32, u32)] {
594        &self.constant_buffers
595    }
596    fn textures(&self) -> &[u32] {
597        &self.textures
598    }
599    fn samplers(&self) -> &[u32] {
600        &self.samplers
601    }
602
603    fn execute(
604        &self,
605        inputs: &[[u32; 4]],
606        buffers: &[(u32, &[[u32; 4]])],
607        sampler: &mut dyn Sampler,
608    ) -> Result<Execution, Error> {
609        self.run_scalar(inputs, None, buffers, sampler)
610    }
611
612    fn execute_with_derivatives(
613        &self,
614        inputs: &[[u32; 4]],
615        derivatives: &[[[u32; 4]; 2]],
616        buffers: &[(u32, &[[u32; 4]])],
617        sampler: &mut dyn Sampler,
618    ) -> Result<Execution, Error> {
619        self.run_scalar(inputs, Some(derivatives), buffers, sampler)
620    }
621
622    fn execute_lanes(
623        &self,
624        inputs: &[&[[u32; 4]]],
625        derivatives: &[[[u32; 4]; 2]],
626        buffers: &[(u32, &[[u32; 4]])],
627        sampler: &mut dyn Sampler,
628        each: &mut dyn FnMut(usize, &Execution),
629    ) -> Result<(), (usize, Error)> {
630        let lane_derivatives = self.derivatives_of(derivatives);
631        let mut room = Room::default();
632        for (k, chunk) in inputs.chunks(LANES).enumerate() {
633            let together = self.run_lanes(
634                chunk,
635                &lane_derivatives,
636                buffers,
637                sampler,
638                &mut room,
639                &mut |i, e| each(k.saturating_mul(LANES).saturating_add(i), e),
640            );
641            // a run that stops is run again one invocation after another: the error is then the first failing
642            // one's, as alone, and the invocations before it are given
643            if together.is_err() {
644                for (i, one) in chunk.iter().enumerate() {
645                    let done = self
646                        .run_scalar(one, Some(derivatives), buffers, sampler)
647                        .map_err(|e| (k.saturating_mul(LANES).saturating_add(i), e))?;
648                    each(k.saturating_mul(LANES).saturating_add(i), &done);
649                }
650            }
651        }
652        Ok(())
653    }
654
655    fn execute_quad(
656        &self,
657        inputs: [&[[u32; 4]]; 4],
658        buffers: &[(u32, &[[u32; 4]])],
659        sampler: &mut dyn Sampler,
660    ) -> Result<[Execution; 4], Error> {
661        let bound = self.bound(buffers)?;
662        let arguments = [
663            self.arguments_of(inputs[0])?,
664            self.arguments_of(inputs[1])?,
665            self.arguments_of(inputs[2])?,
666            self.arguments_of(inputs[3])?,
667        ];
668        let mut backend = self.backend(sampler);
669        let scratch = take(&self.quad_scratch).unwrap_or_else(|| Scratch::new(&self.executable));
670        let mut runner = Runner::<4>::with_scratch(&self.executable, &bound, &mut backend, None, scratch);
671        let outputs = runner.run([&arguments[0], &arguments[1], &arguments[2], &arguments[3]]);
672        give_back(&self.quad_scratch, runner.into_scratch());
673        Ok(outputs.map_err(error)?.map(|o| self.execution(o)))
674    }
675}
676
677impl ProgramShader {
678    /// Run a vertex program once on its input registers (the vertex's attributes): its varyings in the register view,
679    /// as [`PixelShader::execute`] gives them, and its clip-space position (`SV_Position`) as f32 bits. A program that
680    /// is not a vertex program, or writes no position, refuses.
681    pub fn execute_vertex(
682        &self,
683        inputs: &[[u32; 4]],
684        buffers: &[(u32, &[[u32; 4]])],
685        sampler: &mut dyn Sampler,
686    ) -> Result<(Execution, [u32; 4]), Error> {
687        let at = self
688            .position
689            .ok_or_else(|| error("not a vertex program that writes a position"))?;
690        let bound = self.bound(buffers)?;
691        let arguments = self.arguments_of(inputs)?;
692        let mut backend = self.backend(sampler);
693        let scratch = take(&self.scalar_scratch).unwrap_or_else(|| Scratch::new(&self.executable));
694        let mut runner = Runner::<1>::with_scratch(&self.executable, &bound, &mut backend, None, scratch);
695        let output = runner.run([&arguments]);
696        give_back(&self.scalar_scratch, runner.into_scratch());
697        let [output] = output.map_err(error)?;
698        let position = output.values.get(at).copied().unwrap_or([0; 4]);
699        Ok((self.execution(output), position))
700    }
701
702    /// [`PixelShader::execute_lanes`] without its second run: the invocations in lockstep, 16 at a time, and on an
703    /// error the one the lockstep run met (of whichever invocation met one first in lockstep, which need not be the
704    /// first to fail alone).
705    pub fn execute_lockstep(
706        &self,
707        inputs: &[&[[u32; 4]]],
708        derivatives: &[[[u32; 4]; 2]],
709        buffers: &[(u32, &[[u32; 4]])],
710        sampler: &mut dyn Sampler,
711    ) -> Result<Vec<Execution>, Error> {
712        let derivatives = self.derivatives_of(derivatives);
713        let mut room = Room::default();
714        let mut out = Vec::with_capacity(inputs.len());
715        for chunk in inputs.chunks(LANES) {
716            self.run_lanes(chunk, &derivatives, buffers, sampler, &mut room, &mut |_, e| {
717                out.push(e.clone())
718            })?;
719        }
720        Ok(out)
721    }
722
723    /// Up to [`LANES`] invocations in lockstep, sharing `derivatives` (the entry arguments'), each one's execution
724    /// given to `each` (with its index in `inputs`) once all have run, in `room`'s buffers.
725    fn run_lanes(
726        &self,
727        inputs: &[&[[u32; 4]]],
728        derivatives: &[[[u32; 4]; 2]],
729        buffers: &[(u32, &[[u32; 4]])],
730        sampler: &mut dyn Sampler,
731        room: &mut Room,
732        each: &mut dyn FnMut(usize, &Execution),
733    ) -> Result<(), Error> {
734        let bound = self.bound(buffers)?;
735        let width = self.arguments.len();
736        room.arguments.clear();
737        for one in inputs {
738            self.arguments_into(one, &mut room.arguments)?;
739        }
740        let count = inputs.len();
741        if count == 0 {
742            return Ok(());
743        }
744        // the lanes past the invocations are given the last one's arguments and not run
745        let arguments = &room.arguments;
746        let args: [&[[u32; 4]]; LANES] = std::array::from_fn(|l| {
747            let i = l.min(count.saturating_sub(1));
748            let from = i.saturating_mul(width);
749            arguments
750                .get(from..from.saturating_add(width))
751                .unwrap_or_default()
752        });
753        let mut backend = self.backend(sampler);
754        let scratch = take(&self.lane_scratch).unwrap_or_else(|| Scratch::new(&self.executable));
755        let mut runner = Runner::<LANES, u16>::with_scratch(
756            &self.executable,
757            &bound,
758            &mut backend,
759            Some(derivatives),
760            scratch,
761        );
762        let run = runner.run_in_place(args, count);
763        if run.is_ok() {
764            for l in 0..count {
765                let (values, discarded, steps, samples) = runner.lane(l);
766                self.execution_into(values, discarded, steps, samples, &mut room.execution);
767                each(l, &room.execution);
768            }
769        }
770        give_back(&self.lane_scratch, runner.into_scratch());
771        run.map_err(error)
772    }
773
774    fn run_scalar(
775        &self,
776        inputs: &[[u32; 4]],
777        derivatives: Option<&[[[u32; 4]; 2]]>,
778        buffers: &[(u32, &[[u32; 4]])],
779        sampler: &mut dyn Sampler,
780    ) -> Result<Execution, Error> {
781        let bound = self.bound(buffers)?;
782        let arguments = self.arguments_of(inputs)?;
783        let derivatives = derivatives.map(|d| self.derivatives_of(d));
784        let mut backend = self.backend(sampler);
785        let scratch = take(&self.scalar_scratch).unwrap_or_else(|| Scratch::new(&self.executable));
786        let mut runner = Runner::<1>::with_scratch(
787            &self.executable,
788            &bound,
789            &mut backend,
790            derivatives.as_deref(),
791            scratch,
792        );
793        let output = runner.run([&arguments]);
794        give_back(&self.scalar_scratch, runner.into_scratch());
795        let [output] = output.map_err(error)?;
796        Ok(self.execution(output))
797    }
798}