use crate::{Device, GpuError};
use std::ops::Range;
pub const MAX_TIMESTAMP_CAPTURE_PASSES: u32 = 2048;
#[derive(Debug, thiserror::Error)]
pub enum TimestampCaptureError {
#[error("pass timestamp queries are unsupported")]
Unsupported,
#[error("invalid timestamp capture capacity {requested}")]
InvalidCapacity {
requested: u32,
},
#[error(transparent)]
Backend(#[from] GpuError),
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum TimestampPassKind {
Render,
Compute,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct TimestampQueryPair {
pub beginning: u32,
pub end: u32,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum PassTimestampAbsence {
DescriptorTimestampWrites,
Unsupported,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct PassTimestampRecord {
pub occurrence: u64,
pub label: &'static str,
pub kind: TimestampPassKind,
pub sample: Option<u32>,
pub queries: Result<TimestampQueryPair, PassTimestampAbsence>,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum TimestampCaptureIncomplete {
CapacityExceeded {
capacity: u32,
omitted_passes: u64,
},
}
#[derive(Debug)]
pub struct PassTimestampCapture<Q> {
queries: Option<Q>,
records: Vec<PassTimestampRecord>,
max_passes: u32,
next_query: u32,
passes_seen: u64,
sample: Option<u32>,
}
impl<Q> PassTimestampCapture<Q> {
pub fn new<D: Device<QuerySet = Q>>(
device: &D,
max_passes: u32,
) -> Result<Self, TimestampCaptureError> {
if max_passes == 0 || max_passes > MAX_TIMESTAMP_CAPTURE_PASSES {
return Err(TimestampCaptureError::InvalidCapacity {
requested: max_passes,
});
}
if !device.capabilities().timestamp_queries() {
return Err(TimestampCaptureError::Unsupported);
}
let queries = device.create_timestamp_query_set(max_passes * 2)?;
Ok(Self {
queries: Some(queries),
records: Vec::with_capacity(max_passes as usize),
max_passes,
next_query: 0,
passes_seen: 0,
sample: None,
})
}
pub fn metadata_only(max_passes: u32) -> Result<Self, TimestampCaptureError> {
if max_passes == 0 || max_passes > MAX_TIMESTAMP_CAPTURE_PASSES {
return Err(TimestampCaptureError::InvalidCapacity {
requested: max_passes,
});
}
Ok(Self {
queries: None,
records: Vec::with_capacity(max_passes as usize),
max_passes,
next_query: 0,
passes_seen: 0,
sample: None,
})
}
#[must_use]
pub const fn queries(&self) -> Option<&Q> {
self.queries.as_ref()
}
#[must_use]
pub const fn query_range(&self) -> Range<u32> {
0..self.next_query
}
#[must_use]
pub const fn query_capacity(&self) -> u32 {
if self.queries.is_some() {
self.max_passes * 2
} else {
0
}
}
#[must_use]
pub fn records(&self) -> &[PassTimestampRecord] {
&self.records
}
#[must_use]
pub const fn passes_seen(&self) -> u64 {
self.passes_seen
}
#[must_use]
pub fn incomplete(&self) -> Option<TimestampCaptureIncomplete> {
let omitted_passes = self.passes_seen - self.records.len() as u64;
(omitted_passes != 0).then_some(TimestampCaptureIncomplete::CapacityExceeded {
capacity: self.max_passes,
omitted_passes,
})
}
pub const fn set_sample(&mut self, sample: Option<u32>) {
self.sample = sample;
}
pub fn reset(&mut self) {
self.records.clear();
self.next_query = 0;
self.passes_seen = 0;
self.sample = None;
}
pub fn record_pass(
&mut self,
label: &'static str,
kind: TimestampPassKind,
descriptor_has_timestamps: bool,
) -> Option<TimestampQueryPair> {
let occurrence = self.passes_seen;
self.passes_seen = self.passes_seen.saturating_add(1);
if self.records.len() == self.max_passes as usize {
return None;
}
let queries = if descriptor_has_timestamps {
Err(PassTimestampAbsence::DescriptorTimestampWrites)
} else if self.queries.is_none() {
Err(PassTimestampAbsence::Unsupported)
} else {
let pair = TimestampQueryPair {
beginning: self.next_query,
end: self.next_query + 1,
};
self.next_query += 2;
Ok(pair)
};
self.records.push(PassTimestampRecord {
occurrence,
label,
kind,
sample: self.sample,
queries,
});
queries.ok()
}
}
#[cfg(test)]
#[path = "timestamp_capture_tests.rs"]
mod tests;