Skip to main content

iridia_exec/probe/
buffers.rs

1//! Read-only structured-buffer answers composed with a sampler's image answers.
2use std::collections::BTreeMap;
3
4use crate::{ImageLoad, ImageQuery, ImageSample, Load, Sample, Sampler};
5
6/// The caller's stated view extent. The adapter validates the stride and addressable word count.
7#[derive(Clone, Copy, Debug, PartialEq, Eq)]
8pub struct BufferExtent {
9    /// Bytes in an element, nonzero and divisible by four.
10    pub stride: u32,
11    /// Elements in the view.
12    pub elements: u32,
13}
14
15/// One word requested from a structured buffer, in the sampler's binding-slot vocabulary.
16#[derive(Clone, Copy, Debug, PartialEq, Eq)]
17pub struct BufferRead {
18    /// The structured buffer's bind slot (`t<n>`).
19    pub slot: u32,
20    /// The element read.
21    pub element: u32,
22    /// Byte offset within the element, aligned to a word.
23    pub offset: u32,
24}
25
26/// A read-only sampler combining image answers (for example [`super::AnsweredReads`]) with finite structured views.
27/// Successful structured reads retain both request and answer; failed reads stop with the request and reason.
28/// Image reads are delegated unchanged. Stores and atomics are refused, even if `images` could answer them.
29/// Buffer lengths come only from `extents`, never from an image sampler's absent-view policy.
30///
31/// An undeclared structured view is an error. An element beyond a declared extent reads zero, D3D's stated view
32/// rule, without calling `answer`. An invalid offset within an element is always refused. The executor may use
33/// [`Sampler::buffer_elements`] to resolve an out-of-range read before calling `structured`; `queries` records those
34/// extent requests, while `reads` records only the structured calls actually made, not a complete instruction trace.
35/// Lifted bounds guards can also read a valid element before masking its value away; the caller must answer every
36/// requested in-range word, not only the element it expects the final output to use.
37#[derive(Debug)]
38pub struct AnsweredBuffers<S, F> {
39    /// The sampler that answers image reads.
40    pub images: S,
41    /// The function that answers each structured read.
42    pub answer: F,
43    extents: BTreeMap<u32, BufferExtent>,
44    /// The structured reads made, each with the word answered.
45    pub reads: Vec<(BufferRead, u32)>,
46    /// The extent requests made: the slot and the element count answered, `None` for an undeclared view.
47    pub queries: Vec<(u32, Option<u32>)>,
48}
49
50impl<S, F> AnsweredBuffers<S, F> {
51    /// Combine `images` with the structured views `extents` declares, by slot. A stride that is zero or not a multiple of
52    /// 4, or an extent whose word count does not fit 32 bits, is refused.
53    pub fn new(images: S, extents: BTreeMap<u32, BufferExtent>, answer: F) -> Result<Self, String> {
54        for (&slot, extent) in &extents {
55            if extent.stride == 0 || !extent.stride.is_multiple_of(4) {
56                return Err(format!(
57                    "t{slot}: structured stride {} is not a positive word multiple",
58                    extent.stride
59                ));
60            }
61            if u64::from(extent.stride / 4).saturating_mul(u64::from(extent.elements)) > u64::from(u32::MAX) {
62                return Err(format!(
63                    "t{slot}: structured extent exceeds the executor's word address range"
64                ));
65            }
66        }
67        Ok(Self {
68            images,
69            answer,
70            extents,
71            reads: Vec::new(),
72            queries: Vec::new(),
73        })
74    }
75}
76
77impl<S: Sampler, F: FnMut(BufferRead) -> Result<u32, String>> Sampler for AnsweredBuffers<S, F> {
78    fn sample(&mut self, request: Sample) -> Result<[f32; 4], String> {
79        self.images.sample(request)
80    }
81
82    fn load(&mut self, request: Load) -> Result<[u32; 4], String> {
83        self.images.load(request)
84    }
85
86    fn sample_image(&mut self, request: ImageSample<u32>) -> Result<[f32; 4], String> {
87        self.images.sample_image(request)
88    }
89
90    fn load_image(&mut self, request: ImageLoad<u32>) -> Result<[u32; 4], String> {
91        self.images.load_image(request)
92    }
93
94    fn query_image(&mut self, request: ImageQuery<u32>) -> Result<[u32; 4], String> {
95        self.images.query_image(request)
96    }
97
98    fn structured(&mut self, slot: u32, element: u32, offset: u32) -> Result<u32, String> {
99        let extent = self
100            .extents
101            .get(&slot)
102            .ok_or_else(|| format!("t{slot}: no structured extent stated"))?;
103        if !offset.is_multiple_of(4) || u64::from(offset) + 4 > u64::from(extent.stride) {
104            return Err(format!(
105                "t{slot}: structured byte offset {offset} outside word-aligned stride {}",
106                extent.stride
107            ));
108        }
109        let request = BufferRead {
110            slot,
111            element,
112            offset,
113        };
114        let value = if element >= extent.elements {
115            0
116        } else {
117            (self.answer)(request)
118                .map_err(|why| format!("t{slot}: element {element} byte {offset}: {why}"))?
119        };
120        self.reads.push((request, value));
121        Ok(value)
122    }
123
124    fn buffer_elements(&mut self, slot: u32) -> Option<u32> {
125        let elements = self.extents.get(&slot).map(|e| e.elements);
126        self.queries.push((slot, elements));
127        elements
128    }
129}
130
131#[cfg(test)]
132#[path = "buffers_tests.rs"]
133mod tests;