Skip to main content

iridia_exec/
pipeline.rs

1//! The ray tracing pipeline on the CPU: `DispatchRays` runs a ray generation program per position, and `TraceRay`
2//! traverses the acceleration structure the caller supplies, running the hit group's any-hit and intersection programs
3//! for the candidates the ray meets, the closest-hit program for the hit it keeps and the miss program when it has none;
4//! a callable program runs for `CallShader`. The programs are the function-form [`Program`]s of a ray tracing library
5//! (see [`iridia::stage_functions`]), run one invocation at a time with [`Executable::function`]; their payloads,
6//! attributes and parameters are passed by value in and out, which is what a pointer to a structure of function memory
7//! is when nothing else touches it.
8//!
9//! What a library states (its hit groups: which programs make a group, of triangles or of procedural primitives) comes
10//! from its subobjects ([`iridia::Subobject`]). What the application states, the shader table, is the caller's: which
11//! hit group a hit index selects, which program a miss index and a callable index select. The hit group index of a hit
12//! is DXR's: the ray's offset, plus its stride times the geometry's index in the bottom level, plus the instance's
13//! contribution (section "Indexing into shader tables" of the specification,
14//! <https://microsoft.github.io/DirectX-Specs/d3d/Raytracing.html>).
15
16use std::collections::BTreeMap;
17use std::sync::Arc;
18
19use super::pixel::SlotBackend;
20use super::rayquery::{Built, Hit, Query, Scene, flags, transform_point, transform_vector};
21use super::{
22    Backend, CallShader, Decision, Element, Executable, ImageAtomic, ImageLoad, ImageQuery, ImageSample,
23    ImageStore, ReportHit, Resources, Runner, TraceRay,
24};
25use crate::Sampler;
26use iridia::{
27    BufferKind, D3dClass, FunctionSig, HitGroup, IoType, Program, ResourceId, ResourceKind, StageFacts,
28    StorageTarget, Subobject, SystemValue, stage_functions,
29};
30
31/// The pipeline flags the pipeline runs: they are added to the flags of every `TraceRay`, as the specification says of
32/// `D3D12_RAYTRACING_PIPELINE_FLAG_SKIP_TRIANGLES` ("add in the RAY_FLAG_SKIP_TRIANGLES Ray flag") and
33/// `SKIP_PROCEDURAL_PRIMITIVES`.
34const SUPPORTED_PIPELINE_FLAGS: u32 = flags::SKIP_TRIANGLES | flags::SKIP_PROCEDURAL;
35
36/// The deepest maximum recursion a pipeline may declare.
37const MAX_DECLARABLE_RECURSION: u32 = 31;
38
39/// The application's shader table: the program each index selects, by the export name the library gives it.
40#[derive(Clone, Debug, Default)]
41pub struct ShaderTable {
42    /// The miss programs, by miss index.
43    pub miss: Vec<String>,
44    /// The hit groups, by hit group index.
45    pub hit_groups: Vec<String>,
46    /// The callable programs, by shader index.
47    pub callable: Vec<String>,
48}
49
50/// What a dispatch binds besides the programs: the constant buffers of the root signature, by register and space, and
51/// the acceleration structure all the programs trace into.
52#[derive(Clone, Debug, Default)]
53pub struct PipelineResources {
54    /// The constant buffers of the root signature, by (register, space): rows of four 32-bit words.
55    pub constants: BTreeMap<(u32, u32), Vec<[u32; 4]>>,
56    /// The acceleration structure every program traces into.
57    pub scene: Scene,
58}
59
60/// What a dispatch did.
61#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
62pub struct DispatchStats {
63    /// Programs run, of every kind.
64    pub programs: u64,
65    /// Rays traced.
66    pub rays: u64,
67}
68
69/// A program ready to run, with the register view its resources have.
70#[derive(Debug)]
71struct Entry {
72    program: Program,
73    executable: Executable,
74    signature: FunctionSig,
75    /// The bind slot of each resource (its register), the kind of each buffer.
76    slots: BTreeMap<ResourceId, u32>,
77    buffers: BTreeMap<ResourceId, BufferKind>,
78}
79
80/// A ray tracing pipeline: the programs of a library, its hit groups and the application's shader table.
81#[derive(Debug)]
82pub struct Pipeline {
83    programs: BTreeMap<String, Entry>,
84    groups: BTreeMap<String, HitGroup>,
85    table: ShaderTable,
86    /// The deepest `TraceRay` recursion the pipeline declares; none until a library's subobjects or
87    /// [`Pipeline::with_max_recursion`] state it.
88    max_recursion: Option<u32>,
89    /// The pipeline flags (`D3D12_RAYTRACING_PIPELINE_CONFIG1`): ray flags every `TraceRay` of the pipeline gets added.
90    config_flags: u32,
91}
92
93impl Pipeline {
94    /// A pipeline of `programs` (each a ray tracing program, named by its export), the hit groups the programs'
95    /// libraries state, and `table`. The pipeline's maximum recursion depth is the one a library's pipeline config
96    /// subobject states; libraries that state none leave it to [`Pipeline::with_max_recursion`], and a dispatch
97    /// without one is refused (the DXR specification: "A raytracing pipeline needs one raytracing pipeline
98    /// configuration", section "D3D12_RAYTRACING_PIPELINE_CONFIG").
99    pub fn new(programs: Vec<Program>, table: ShaderTable) -> Result<Self, String> {
100        let mut entries = BTreeMap::new();
101        let mut groups = BTreeMap::new();
102        let mut max_recursion = None;
103        let mut config_flags = None;
104        for program in programs {
105            let StageFacts::RayTracing(facts) = program.interface.facts.clone() else {
106                return Err(format!(
107                    "a {:?} program is not a ray tracing program",
108                    program.stage
109                ));
110            };
111            for sub in &facts.subobjects {
112                match sub {
113                    Subobject::HitGroup(g) => {
114                        groups.insert(g.name.clone(), g.clone());
115                    }
116                    Subobject::PipelineConfig {
117                        max_recursion_depth,
118                        flags,
119                        ..
120                    } => {
121                        // a config without flags (`D3D12_RAYTRACING_PIPELINE_CONFIG`) has none set
122                        let flags = flags.unwrap_or(0);
123                        if config_flags.is_some_and(|f| f != flags) {
124                            return Err(format!(
125                                "the libraries state different pipeline flags ({:#x} and {flags:#x})",
126                                config_flags.unwrap_or_default()
127                            ));
128                        }
129                        config_flags = Some(flags);
130                        // "If multiple [...] configurations are present [...] they must all match"
131                        if max_recursion.is_some_and(|d| d != *max_recursion_depth) {
132                            return Err(format!(
133                                "the libraries state different maximum recursion depths ({} and {max_recursion_depth})",
134                                max_recursion.unwrap_or_default()
135                            ));
136                        }
137                        max_recursion = Some(*max_recursion_depth);
138                    }
139                    _ => {}
140                }
141            }
142            let signature = stage_functions(&program.interface)?
143                .and_then(|mut f| f.pop())
144                .ok_or("a ray tracing program without its function")?;
145            let executable =
146                Executable::function(&program, "main").map_err(|e| format!("{}: {e}", facts.export))?;
147            let mut slots = BTreeMap::new();
148            let mut buffers = BTreeMap::new();
149            for r in &program.interface.resources {
150                let Some(binding) = r.provenance.d3d else {
151                    return Err(format!("resource {} has no D3D bind slot", r.provenance.name));
152                };
153                slots.insert(r.id, binding.register);
154                if let ResourceKind::Buffer(kind)
155                | ResourceKind::Storage {
156                    target: StorageTarget::Buffer(kind),
157                    ..
158                } = &r.kind
159                {
160                    buffers.insert(r.id, *kind);
161                }
162            }
163            entries.insert(
164                facts.export.clone(),
165                Entry {
166                    program,
167                    executable,
168                    signature,
169                    slots,
170                    buffers,
171                },
172            );
173        }
174        Ok(Self {
175            programs: entries,
176            groups,
177            table,
178            max_recursion,
179            config_flags: config_flags.unwrap_or(0),
180        })
181    }
182
183    /// State the maximum recursion depth, the pipeline config an application gives when its libraries do not (the
184    /// depth the application declares in `D3D12_RAYTRACING_PIPELINE_CONFIG`): replaces the libraries' own.
185    pub fn with_max_recursion(mut self, depth: u32) -> Self {
186        self.max_recursion = Some(depth);
187        self
188    }
189
190    /// State the pipeline flags (`D3D12_RAYTRACING_PIPELINE_CONFIG1`'s `Flags`: the SKIP_TRIANGLES and
191    /// SKIP_PROCEDURAL ray flags), as an application gives them when its libraries state none: replaces the
192    /// libraries' own.
193    pub fn with_pipeline_flags(mut self, flags: u32) -> Self {
194        self.config_flags = flags;
195        self
196    }
197
198    /// Add or replace hit groups the libraries do not state.
199    pub fn with_hit_groups(mut self, groups: impl IntoIterator<Item = HitGroup>) -> Self {
200        for g in groups {
201            self.groups.insert(g.name.clone(), g);
202        }
203        self
204    }
205
206    /// Run the ray generation program `raygen` at every position of `dimensions`, x fastest. Image writes and buffer
207    /// access go to `sampler` by bind slot (the register), as for a pixel program.
208    pub fn dispatch_rays(
209        &self,
210        raygen: &str,
211        dimensions: [u32; 3],
212        resources: &PipelineResources,
213        sampler: &mut dyn Sampler,
214    ) -> Result<DispatchStats, String> {
215        let Some(depth) = self.max_recursion else {
216            return Err(
217                "the pipeline has no maximum recursion depth: a raytracing pipeline needs one raytracing pipeline \
218                configuration (DXR specification, D3D12_RAYTRACING_PIPELINE_CONFIG); state it in a library's \
219                subobjects or with Pipeline::with_max_recursion"
220                    .into(),
221            );
222        };
223        if depth > MAX_DECLARABLE_RECURSION {
224            return Err(format!(
225                "the pipeline declares a maximum recursion depth of {depth}, past the {MAX_DECLARABLE_RECURSION} DXR allows \
226                (\"Ray recursion limit\": a depth in the range [0..31])"
227            ));
228        }
229        if self.config_flags & !SUPPORTED_PIPELINE_FLAGS != 0 {
230            return Err(format!(
231                "the pipeline has flags {:#x}; only RAYTRACING_PIPELINE_FLAG_SKIP_TRIANGLES (0x100) and \
232                SKIP_PROCEDURAL_PRIMITIVES (0x200) are run",
233                self.config_flags
234            ));
235        }
236        if self.config_flags & SUPPORTED_PIPELINE_FLAGS == SUPPORTED_PIPELINE_FLAGS {
237            return Err(
238                "the pipeline has both RAYTRACING_PIPELINE_FLAG_SKIP_TRIANGLES and SKIP_PROCEDURAL_PRIMITIVES: \
239                every ray would skip all geometry, which the ray flags forbid"
240                    .into(),
241            );
242        }
243        let entry = self
244            .programs
245            .get(raygen)
246            .ok_or_else(|| format!("no program {raygen}"))?;
247        if entry.program.stage != iridia::Stage::RayGeneration {
248            return Err(format!(
249                "{raygen} is a {:?} program, not a ray generation program",
250                entry.program.stage
251            ));
252        }
253        let built = Arc::new(Built::new(&resources.scene)?);
254        let mut bound = BTreeMap::new();
255        for (name, entry) in &self.programs {
256            let mut r = Resources::default();
257            for res in &entry.program.interface.resources {
258                let Some(binding) = res.provenance.d3d else {
259                    continue;
260                };
261                if matches!(res.kind, ResourceKind::ConstantBuffer { .. })
262                    && binding.class == D3dClass::ConstantBuffer
263                    && let Some(rows) = resources.constants.get(&(binding.register, binding.space))
264                {
265                    r.constants.insert(res.id, rows.clone());
266                }
267            }
268            bound.insert(name.as_str(), r);
269        }
270        let mut host = Host {
271            pipe: self,
272            built,
273            resources: bound,
274            sampler,
275            stack: Vec::new(),
276            active: Vec::new(),
277            stats: DispatchStats::default(),
278            depth: 0,
279        };
280        for z in 0..dimensions[2] {
281            for y in 0..dimensions[1] {
282                for x in 0..dimensions[0] {
283                    let env = Env {
284                        index: [x, y, z],
285                        dimensions,
286                        ..Env::default()
287                    };
288                    host.run(raygen, &env, &mut [], &[])
289                        .map_err(|e| format!("dispatch ({x}, {y}, {z}): {e}"))?;
290                }
291            }
292        }
293        Ok(host.stats)
294    }
295
296    fn group_of(&self, built: &Built, offset: u32, stride: u32, hit: &Hit) -> Option<&HitGroup> {
297        let index = offset
298            .wrapping_add(stride.wrapping_mul(hit.geometry))
299            .wrapping_add(built.instance(hit.instance).contribution);
300        self.table
301            .hit_groups
302            .get(index as usize)
303            .and_then(|name| self.groups.get(name))
304    }
305}
306
307/// The ray state the system values of a program read.
308#[derive(Clone, Debug, Default)]
309struct Env {
310    index: [u32; 3],
311    dimensions: [u32; 3],
312    world_origin: [f32; 3],
313    world_direction: [f32; 3],
314    t_min: f32,
315    t_current: f32,
316    flags: u32,
317    hit: Option<HitEnv>,
318}
319
320/// The hit a program runs for.
321#[derive(Clone, Debug)]
322struct HitEnv {
323    instance: u32,
324    id: u32,
325    geometry: u32,
326    primitive: u32,
327    kind: u32,
328    object_to_world: [[f32; 4]; 3],
329    world_to_object: [[f32; 4]; 3],
330}
331
332/// A `TraceRay` in progress.
333struct Active {
334    query: Query,
335    offset: u32,
336    stride: u32,
337    flags: u32,
338    origin: [f32; 3],
339    direction: [f32; 3],
340    /// The payload as the programs so far left it.
341    payload: Vec<u32>,
342    /// The attributes and kind of the last procedural hit committed (the query holds the hit itself, a triangle's
343    /// attributes being its barycentrics).
344    procedural: Option<(Vec<u32>, u32)>,
345    /// The group of the procedural candidate whose intersection program is running.
346    group: Option<HitGroup>,
347    ended: bool,
348}
349
350struct Host<'p, 's> {
351    pipe: &'p Pipeline,
352    built: Arc<Built>,
353    resources: BTreeMap<&'p str, Resources>,
354    sampler: &'s mut dyn Sampler,
355    /// The programs running, innermost last: whose resources a request names.
356    stack: Vec<&'p Entry>,
357    active: Vec<Active>,
358    stats: DispatchStats,
359    depth: u32,
360}
361
362fn pack(words: &[u32]) -> Vec<[u32; 4]> {
363    words
364        .chunks(4)
365        .map(|c| std::array::from_fn(|i| c.get(i).copied().unwrap_or(0)))
366        .collect()
367}
368
369impl<'p> Host<'p, '_> {
370    /// Run program `name` once with `env`, its memory arguments the words of `memory` (the payload, then the
371    /// attributes; or the parameter), which it leaves as it ends. What the program decided about the hit comes back.
372    fn run(
373        &mut self,
374        name: &str,
375        env: &Env,
376        memory: &mut [&mut Vec<u32>],
377        attributes: &[u32],
378    ) -> Result<Decision, String> {
379        let pipe: &'p Pipeline = self.pipe;
380        let (key, entry) = pipe
381            .programs
382            .get_key_value(name)
383            .ok_or_else(|| format!("the shader table names {name}, which the library does not export"))?;
384        if u32::try_from(self.stack.len()).unwrap_or(u32::MAX)
385            > pipe
386                .max_recursion
387                .unwrap_or(0)
388                .saturating_mul(4)
389                .saturating_add(8)
390        {
391            return Err("the programs call each other too deeply".into());
392        }
393        let interface = &entry.program.interface;
394        let mut registers: Vec<[u32; 4]> = Vec::new();
395        let mut taken = 0;
396        for item in &entry.signature.arguments {
397            match item.ty {
398                IoType::Value(_) => {
399                    let v = item
400                        .varying
401                        .and_then(|i| interface.inputs.get(i))
402                        .ok_or_else(|| format!("{name}: the argument {} is no input", item.name))?;
403                    let system = v
404                        .system
405                        .ok_or_else(|| format!("{name}: the input {} is no system value", item.name))?;
406                    registers.push(system_value(system, v.label.index, env)?);
407                }
408                IoType::Memory => {
409                    // payload (or parameter) first, the hit attributes second
410                    let words = match taken {
411                        0 if memory.is_empty() => Vec::new(),
412                        0 => memory.first().map(|m| m.to_vec()).unwrap_or_default(),
413                        _ => attributes.to_vec(),
414                    };
415                    let size = entry
416                        .executable
417                        .pointer_words(taken)
418                        .ok_or_else(|| format!("{name}: no pointer argument {taken}"))?;
419                    let mut words = words;
420                    words.resize(size as usize, 0);
421                    registers.extend(pack(&words));
422                    taken = taken.saturating_add(1);
423                }
424                _ => {
425                    return Err(format!(
426                        "{name}: the argument {} is of a kind a ray tracing program does not take",
427                        item.name
428                    ));
429                }
430            }
431        }
432        let resources = self
433            .resources
434            .get(key.as_str())
435            .cloned()
436            .ok_or_else(|| format!("{name}: the pipeline has no resources for it"))?;
437        let mut bound = entry
438            .executable
439            .bind_resources(&resources)
440            .map_err(|e| format!("{name}: {e}"))?;
441        for r in &interface.resources {
442            if matches!(r.kind, ResourceKind::AccelerationStructure) {
443                bound = bound.with_scene(r.id, self.built.clone());
444            }
445        }
446        self.stack.push(entry);
447        self.depth = self.depth.saturating_add(1);
448        self.stats.programs = self.stats.programs.saturating_add(1);
449        let result = {
450            let mut runner = Runner::<1>::new(&entry.executable, &bound, &mut *self, None);
451            runner.run([&registers])
452        };
453        self.depth = self.depth.saturating_sub(1);
454        self.stack.pop();
455        let [out] = result.map_err(|e| format!("{name}: {e}"))?;
456        for (k, m) in memory.iter_mut().enumerate() {
457            if let Some(words) = out.pointees.get(k) {
458                m.clear();
459                m.extend_from_slice(words);
460            }
461        }
462        Ok(out.decision)
463    }
464
465    fn top(&mut self) -> Result<&mut Active, String> {
466        self.active
467            .last_mut()
468            .ok_or_else(|| "a hit reported outside a ray".to_owned())
469    }
470
471    fn last(&self) -> Result<&Active, String> {
472        self.active
473            .last()
474            .ok_or_else(|| "a hit reported outside a ray".to_owned())
475    }
476
477    /// The environment a program of a hit runs in.
478    fn env_for(built: &Built, a: &Active, hit: &Hit, t_current: f32, kind: u32) -> Env {
479        let instance = built.instance(hit.instance);
480        let inverse = *built.inverse(hit.instance);
481        Env {
482            world_origin: a.origin,
483            world_direction: a.direction,
484            t_min: a.query.tmin,
485            t_current,
486            flags: a.flags,
487            hit: Some(HitEnv {
488                instance: hit.instance,
489                id: instance.id & 0x00ff_ffff,
490                geometry: hit.geometry,
491                primitive: hit.primitive,
492                kind,
493                object_to_world: instance.transform,
494                world_to_object: inverse,
495            }),
496            ..Env::default()
497        }
498    }
499
500    fn trace(&mut self, r: TraceRay<'_>) -> Result<(), String> {
501        if u32::try_from(self.active.len()).unwrap_or(u32::MAX) >= self.pipe.max_recursion.unwrap_or(0) {
502            return Err(format!(
503                "TraceRay recurses deeper than the pipeline's maximum of {}",
504                self.pipe.max_recursion.unwrap_or(0)
505            ));
506        }
507        self.stats.rays = self.stats.rays.saturating_add(1);
508        let ray_flags = r.flags | self.pipe.config_flags;
509        let query = Query::new(
510            r.scene,
511            ray_flags,
512            r.mask,
513            r.t_min,
514            r.t_max,
515            r.origin,
516            r.direction,
517        );
518        self.active.push(Active {
519            query,
520            offset: r.hit_group_offset,
521            stride: r.hit_group_stride,
522            flags: ray_flags,
523            origin: r.origin,
524            direction: r.direction,
525            payload: r.payload.to_vec(),
526            procedural: None,
527            group: None,
528            ended: false,
529        });
530        let outcome = self.trace_active(r.miss_index);
531        let done = self.active.pop();
532        outcome?;
533        if let Some(a) = done {
534            let n = r.payload.len().min(a.payload.len());
535            if let (Some(to), Some(from)) = (r.payload.get_mut(..n), a.payload.get(..n)) {
536                to.copy_from_slice(from);
537            }
538        }
539        Ok(())
540    }
541
542    fn trace_active(&mut self, miss_index: u32) -> Result<(), String> {
543        let built = self.built.clone();
544        loop {
545            let (more, offset, stride) = {
546                let a = self.top()?;
547                (a.query.proceed(&built), a.offset, a.stride)
548            };
549            if !more {
550                break;
551            }
552            let candidate = self.top()?.query.candidate.ok_or("a candidate that is gone")?;
553            let group = self.pipe.group_of(&built, offset, stride, &candidate).cloned();
554            match candidate.kind {
555                1 => {
556                    let attributes = vec![
557                        candidate.barycentrics[0].to_bits(),
558                        candidate.barycentrics[1].to_bits(),
559                    ];
560                    let decision = match group.as_ref().and_then(|g| g.any_hit.clone()) {
561                        Some(name) => self.run_hit_program(&name, &candidate, candidate.t, &attributes)?,
562                        None => Decision::None,
563                    };
564                    let a = self.top()?;
565                    match decision {
566                        Decision::Ignore => {}
567                        Decision::None | Decision::AcceptAndEnd => {
568                            a.query.confirm()?;
569                            if decision == Decision::AcceptAndEnd {
570                                a.query.terminate();
571                            }
572                        }
573                    }
574                }
575                3 => {
576                    let Some(name) = group.as_ref().and_then(|g| g.intersection.clone()) else {
577                        continue;
578                    };
579                    self.top()?.group = group;
580                    let tcurrent = self.top()?.query.tmax();
581                    let env = { Self::env_for(&self.built, self.last()?, &candidate, tcurrent, 0) };
582                    let mut none: [&mut Vec<u32>; 0] = [];
583                    self.run(&name, &env, &mut none, &[])?;
584                    let a = self.top()?;
585                    a.group = None;
586                    if a.ended {
587                        break;
588                    }
589                }
590                other => return Err(format!("a candidate of kind {other}")),
591            }
592        }
593        // the closest hit, or the miss
594        let (committed, ray_flags, offset, stride) = {
595            let a = self.top()?;
596            let hit = a.query.committed;
597            let (attributes, kind) = match hit {
598                Some(h) if h.kind == 1 => (
599                    vec![h.barycentrics[0].to_bits(), h.barycentrics[1].to_bits()],
600                    if h.front { 254 } else { 255 },
601                ),
602                _ => a.procedural.clone().unwrap_or_default(),
603            };
604            (hit.map(|h| (h, attributes, kind)), a.flags, a.offset, a.stride)
605        };
606        match committed {
607            Some((hit, attributes, kind)) => {
608                if ray_flags & flags::SKIP_CLOSEST_HIT == 0
609                    && let Some(name) = self
610                        .pipe
611                        .group_of(&built, offset, stride, &hit)
612                        .and_then(|g| g.closest_hit.clone())
613                {
614                    let env = Self::env_for(&self.built, self.last()?, &hit, hit.t, kind);
615                    let mut payload = std::mem::take(&mut self.top()?.payload);
616                    let result = self.run(&name, &env, &mut [&mut payload], &attributes);
617                    self.top()?.payload = payload;
618                    result?;
619                }
620            }
621            None => {
622                if let Some(name) = self.pipe.table.miss.get(miss_index as usize).cloned() {
623                    let env = {
624                        let a = self.top()?;
625                        Env {
626                            world_origin: a.origin,
627                            world_direction: a.direction,
628                            t_min: a.query.tmin,
629                            t_current: a.query.tmax(),
630                            flags: a.flags,
631                            ..Env::default()
632                        }
633                    };
634                    let mut payload = std::mem::take(&mut self.top()?.payload);
635                    let result = self.run(&name, &env, &mut [&mut payload], &[]);
636                    self.top()?.payload = payload;
637                    result?;
638                }
639            }
640        }
641        Ok(())
642    }
643
644    /// Run an any-hit program for a candidate with the payload of the ray in progress.
645    fn run_hit_program(
646        &mut self,
647        name: &str,
648        hit: &Hit,
649        t_current: f32,
650        attributes: &[u32],
651    ) -> Result<Decision, String> {
652        let kind = if hit.kind == 1 {
653            if hit.front { 254 } else { 255 }
654        } else {
655            0
656        };
657        let env = { Self::env_for(&self.built, self.last()?, hit, t_current, kind) };
658        let mut payload = std::mem::take(&mut self.top()?.payload);
659        let result = self.run(name, &env, &mut [&mut payload], attributes);
660        self.top()?.payload = payload;
661        result
662    }
663
664    fn slot_backend(&mut self) -> Result<SlotBackend<'_>, String> {
665        let entry = *self.stack.last().ok_or("a request outside a program")?;
666        Ok(SlotBackend {
667            sampler: &mut *self.sampler,
668            slots: &entry.slots,
669            buffers: &entry.buffers,
670        })
671    }
672}
673
674/// A system value as the register a program takes it in.
675fn system_value(system: SystemValue, index: u32, env: &Env) -> Result<[u32; 4], String> {
676    let f3 = |v: [f32; 3]| [v[0].to_bits(), v[1].to_bits(), v[2].to_bits(), 0];
677    let hit = || {
678        env.hit
679            .as_ref()
680            .ok_or("a hit's value in a program that runs without a hit")
681    };
682    let object = |point: bool| -> Result<[u32; 4], String> {
683        let h = hit()?;
684        let (o, d) = (
685            transform_point(&h.world_to_object, env.world_origin),
686            transform_vector(&h.world_to_object, env.world_direction),
687        );
688        Ok(f3(if point { o } else { d }))
689    };
690    Ok(match system {
691        SystemValue::DispatchRaysIndex => [env.index[0], env.index[1], env.index[2], 0],
692        SystemValue::DispatchRaysDimensions => [env.dimensions[0], env.dimensions[1], env.dimensions[2], 0],
693        SystemValue::WorldRayOrigin => f3(env.world_origin),
694        SystemValue::WorldRayDirection => f3(env.world_direction),
695        SystemValue::ObjectRayOrigin => object(true)?,
696        SystemValue::ObjectRayDirection => object(false)?,
697        SystemValue::RayTMin => [env.t_min.to_bits(), 0, 0, 0],
698        SystemValue::RayTCurrent => [env.t_current.to_bits(), 0, 0, 0],
699        SystemValue::RayFlags => [env.flags, 0, 0, 0],
700        SystemValue::InstanceId => [hit()?.id, 0, 0, 0],
701        SystemValue::InstanceIndex => [hit()?.instance, 0, 0, 0],
702        SystemValue::GeometryIndex => [hit()?.geometry, 0, 0, 0],
703        SystemValue::PrimitiveIndex => [hit()?.primitive, 0, 0, 0],
704        SystemValue::HitKind => [hit()?.kind, 0, 0, 0],
705        SystemValue::ObjectToWorld => hit()?
706            .object_to_world
707            .get(index as usize)
708            .map(|r| r.map(f32::to_bits))
709            .ok_or("a transform row past the third")?,
710        SystemValue::WorldToObject => hit()?
711            .world_to_object
712            .get(index as usize)
713            .map(|r| r.map(f32::to_bits))
714            .ok_or("a transform row past the third")?,
715        other => {
716            return Err(format!(
717                "the system value {other:?} is not one a ray tracing program has"
718            ));
719        }
720    })
721}
722
723impl Backend for Host<'_, '_> {
724    fn sample(&mut self, r: ImageSample<Element>) -> Result<[f32; 4], String> {
725        self.slot_backend()?.sample(r)
726    }
727    fn load(&mut self, r: ImageLoad<Element>) -> Result<[u32; 4], String> {
728        self.slot_backend()?.load(r)
729    }
730    fn query(&mut self, r: ImageQuery<Element>) -> Result<[u32; 4], String> {
731        self.slot_backend()?.query(r)
732    }
733    fn store_image(&mut self, r: ImageStore<ResourceId>) -> Result<(), String> {
734        self.slot_backend()?.store_image(r)
735    }
736    fn atomic_image(&mut self, r: ImageAtomic<ResourceId>) -> Result<u32, String> {
737        self.slot_backend()?.atomic_image(r)
738    }
739    fn store_buffer(&mut self, view: ResourceId, word: u32, value: [u32; 4], mask: u8) -> Result<(), String> {
740        self.slot_backend()?.store_buffer(view, word, value, mask)
741    }
742    fn read_buffer(&mut self, view: ResourceId, word: u32, count: u8) -> Result<[u32; 4], String> {
743        self.slot_backend()?.read_buffer(view, word, count)
744    }
745    fn buffer_words(&mut self, view: ResourceId) -> Result<u32, String> {
746        self.slot_backend()?.buffer_words(view)
747    }
748    fn atomic_add(&mut self, view: ResourceId, word: u32, value: u32) -> Result<u32, String> {
749        self.slot_backend()?.atomic_add(view, word, value)
750    }
751
752    fn trace_ray(&mut self, r: TraceRay<'_>) -> Result<(), String> {
753        self.trace(r)
754    }
755
756    fn call_shader(&mut self, r: CallShader<'_>) -> Result<(), String> {
757        let name = self
758            .pipe
759            .table
760            .callable
761            .get(r.index as usize)
762            .cloned()
763            .ok_or_else(|| {
764                format!(
765                    "CallShader({}): the shader table has no callable program there",
766                    r.index
767                )
768            })?;
769        let env = Env::default();
770        let mut parameter = r.parameter.to_vec();
771        self.run(&name, &env, &mut [&mut parameter], &[])?;
772        let n = r.parameter.len().min(parameter.len());
773        if let (Some(to), Some(from)) = (r.parameter.get_mut(..n), parameter.get(..n)) {
774            to.copy_from_slice(from);
775        }
776        Ok(())
777    }
778
779    fn report_hit(&mut self, r: ReportHit<'_>) -> Result<bool, String> {
780        let (candidate, group, in_range) = {
781            let a = self.top()?;
782            let candidate = a
783                .query
784                .candidate
785                .ok_or("ReportHit outside an intersection program")?;
786            // section "ReportHit": a hit is rejected if t is outside the current ray interval
787            (
788                candidate,
789                a.group.clone(),
790                a.query.tmin <= r.t && r.t <= a.query.tmax(),
791            )
792        };
793        if !in_range {
794            return Ok(false);
795        }
796        let attributes = r.attributes.to_vec();
797        let mut hit = candidate;
798        hit.t = r.t;
799        // a non-opaque geometry offers the hit to the group's any-hit program first
800        let decision = match group.as_ref().and_then(|g| g.any_hit.clone()) {
801            Some(name) if !candidate.opaque => self.run_hit_program(&name, &hit, r.t, &attributes)?,
802            _ => Decision::None,
803        };
804        if decision == Decision::Ignore {
805            return Ok(false);
806        }
807        let a = self.top()?;
808        a.query.generate(r.t)?;
809        a.procedural = Some((attributes, r.hit_kind));
810        if decision == Decision::AcceptAndEnd {
811            a.ended = true;
812            a.query.terminate();
813        }
814        Ok(true)
815    }
816}