1use 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
31const SUPPORTED_PIPELINE_FLAGS: u32 = flags::SKIP_TRIANGLES | flags::SKIP_PROCEDURAL;
35
36const MAX_DECLARABLE_RECURSION: u32 = 31;
38
39#[derive(Clone, Debug, Default)]
41pub struct ShaderTable {
42 pub miss: Vec<String>,
44 pub hit_groups: Vec<String>,
46 pub callable: Vec<String>,
48}
49
50#[derive(Clone, Debug, Default)]
53pub struct PipelineResources {
54 pub constants: BTreeMap<(u32, u32), Vec<[u32; 4]>>,
56 pub scene: Scene,
58}
59
60#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
62pub struct DispatchStats {
63 pub programs: u64,
65 pub rays: u64,
67}
68
69#[derive(Debug)]
71struct Entry {
72 program: Program,
73 executable: Executable,
74 signature: FunctionSig,
75 slots: BTreeMap<ResourceId, u32>,
77 buffers: BTreeMap<ResourceId, BufferKind>,
78}
79
80#[derive(Debug)]
82pub struct Pipeline {
83 programs: BTreeMap<String, Entry>,
84 groups: BTreeMap<String, HitGroup>,
85 table: ShaderTable,
86 max_recursion: Option<u32>,
89 config_flags: u32,
91}
92
93impl Pipeline {
94 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 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 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 pub fn with_max_recursion(mut self, depth: u32) -> Self {
186 self.max_recursion = Some(depth);
187 self
188 }
189
190 pub fn with_pipeline_flags(mut self, flags: u32) -> Self {
194 self.config_flags = flags;
195 self
196 }
197
198 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 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#[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#[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
332struct Active {
334 query: Query,
335 offset: u32,
336 stride: u32,
337 flags: u32,
338 origin: [f32; 3],
339 direction: [f32; 3],
340 payload: Vec<u32>,
342 procedural: Option<(Vec<u32>, u32)>,
345 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 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 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 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([®isters])
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 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 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 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
674fn 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 (
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 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}