molgfx_gpu/
timestamp_capture.rs1use crate::{Device, GpuError};
8use std::ops::Range;
9
10pub const MAX_TIMESTAMP_CAPTURE_PASSES: u32 = 2048;
12
13#[derive(Debug, thiserror::Error)]
15pub enum TimestampCaptureError {
16 #[error("pass timestamp queries are unsupported")]
18 Unsupported,
19 #[error("invalid timestamp capture capacity {requested}")]
21 InvalidCapacity {
22 requested: u32,
24 },
25 #[error(transparent)]
27 Backend(#[from] GpuError),
28}
29
30#[derive(Clone, Copy, Debug, PartialEq, Eq)]
32pub enum TimestampPassKind {
33 Render,
35 Compute,
37}
38
39#[derive(Clone, Copy, Debug, PartialEq, Eq)]
41pub struct TimestampQueryPair {
42 pub beginning: u32,
44 pub end: u32,
46}
47
48#[derive(Clone, Copy, Debug, PartialEq, Eq)]
50pub enum PassTimestampAbsence {
51 DescriptorTimestampWrites,
53 Unsupported,
55}
56
57#[derive(Clone, Copy, Debug, PartialEq, Eq)]
59pub struct PassTimestampRecord {
60 pub occurrence: u64,
62 pub label: &'static str,
64 pub kind: TimestampPassKind,
66 pub sample: Option<u32>,
68 pub queries: Result<TimestampQueryPair, PassTimestampAbsence>,
70}
71
72#[derive(Clone, Copy, Debug, PartialEq, Eq)]
74pub enum TimestampCaptureIncomplete {
75 CapacityExceeded {
77 capacity: u32,
79 omitted_passes: u64,
81 },
82}
83
84#[derive(Debug)]
86pub struct PassTimestampCapture<Q> {
87 queries: Option<Q>,
88 records: Vec<PassTimestampRecord>,
89 max_passes: u32,
90 next_query: u32,
91 passes_seen: u64,
92 sample: Option<u32>,
93}
94
95impl<Q> PassTimestampCapture<Q> {
96 pub fn new<D: Device<QuerySet = Q>>(
101 device: &D,
102 max_passes: u32,
103 ) -> Result<Self, TimestampCaptureError> {
104 if max_passes == 0 || max_passes > MAX_TIMESTAMP_CAPTURE_PASSES {
105 return Err(TimestampCaptureError::InvalidCapacity {
106 requested: max_passes,
107 });
108 }
109 if !device.capabilities().timestamp_queries() {
110 return Err(TimestampCaptureError::Unsupported);
111 }
112 let queries = device.create_timestamp_query_set(max_passes * 2)?;
113 Ok(Self {
114 queries: Some(queries),
115 records: Vec::with_capacity(max_passes as usize),
116 max_passes,
117 next_query: 0,
118 passes_seen: 0,
119 sample: None,
120 })
121 }
122
123 pub fn metadata_only(max_passes: u32) -> Result<Self, TimestampCaptureError> {
129 if max_passes == 0 || max_passes > MAX_TIMESTAMP_CAPTURE_PASSES {
130 return Err(TimestampCaptureError::InvalidCapacity {
131 requested: max_passes,
132 });
133 }
134 Ok(Self {
135 queries: None,
136 records: Vec::with_capacity(max_passes as usize),
137 max_passes,
138 next_query: 0,
139 passes_seen: 0,
140 sample: None,
141 })
142 }
143
144 #[must_use]
146 pub const fn queries(&self) -> Option<&Q> {
147 self.queries.as_ref()
148 }
149
150 #[must_use]
152 pub const fn query_range(&self) -> Range<u32> {
153 0..self.next_query
154 }
155
156 #[must_use]
158 pub const fn query_capacity(&self) -> u32 {
159 if self.queries.is_some() {
160 self.max_passes * 2
161 } else {
162 0
163 }
164 }
165
166 #[must_use]
168 pub fn records(&self) -> &[PassTimestampRecord] {
169 &self.records
170 }
171
172 #[must_use]
174 pub const fn passes_seen(&self) -> u64 {
175 self.passes_seen
176 }
177
178 #[must_use]
180 pub fn incomplete(&self) -> Option<TimestampCaptureIncomplete> {
181 let omitted_passes = self.passes_seen - self.records.len() as u64;
182 (omitted_passes != 0).then_some(TimestampCaptureIncomplete::CapacityExceeded {
183 capacity: self.max_passes,
184 omitted_passes,
185 })
186 }
187
188 pub const fn set_sample(&mut self, sample: Option<u32>) {
190 self.sample = sample;
191 }
192
193 pub fn reset(&mut self) {
196 self.records.clear();
197 self.next_query = 0;
198 self.passes_seen = 0;
199 self.sample = None;
200 }
201
202 pub fn record_pass(
205 &mut self,
206 label: &'static str,
207 kind: TimestampPassKind,
208 descriptor_has_timestamps: bool,
209 ) -> Option<TimestampQueryPair> {
210 let occurrence = self.passes_seen;
211 self.passes_seen = self.passes_seen.saturating_add(1);
212 if self.records.len() == self.max_passes as usize {
213 return None;
214 }
215 let queries = if descriptor_has_timestamps {
216 Err(PassTimestampAbsence::DescriptorTimestampWrites)
217 } else if self.queries.is_none() {
218 Err(PassTimestampAbsence::Unsupported)
219 } else {
220 let pair = TimestampQueryPair {
221 beginning: self.next_query,
222 end: self.next_query + 1,
223 };
224 self.next_query += 2;
225 Ok(pair)
226 };
227 self.records.push(PassTimestampRecord {
228 occurrence,
229 label,
230 kind,
231 sample: self.sample,
232 queries,
233 });
234 queries.ok()
235 }
236}
237
238#[cfg(test)]
239#[path = "timestamp_capture_tests.rs"]
240mod tests;