use crate::gpu::readback::{Readback, Readbacks};
const SLOTS: usize = 4;
const KEPT: usize = 15;
struct Slot {
set: wgpu::QuerySet,
resolved: wgpu::Buffer,
pending: Option<Readback>,
}
pub(crate) struct Timer {
slots: Vec<Slot>,
current: Option<usize>,
period: f64,
measured: Vec<f32>,
pool: Readbacks,
}
impl Timer {
pub(crate) fn new(device: &wgpu::Device, queue: &wgpu::Queue) -> Option<Timer> {
if !device.features().contains(wgpu::Features::TIMESTAMP_QUERY) {
return None;
}
let slot = |_| Slot {
set: device.create_query_set(&wgpu::QuerySetDescriptor {
label: Some("latkit timer"),
ty: wgpu::QueryType::Timestamp,
count: 2,
}),
resolved: device.create_buffer(&wgpu::BufferDescriptor {
label: Some("latkit timer"),
size: 16,
usage: wgpu::BufferUsages::QUERY_RESOLVE | wgpu::BufferUsages::COPY_SRC,
mapped_at_creation: false,
}),
pending: None,
};
Some(Timer {
slots: (0..SLOTS).map(slot).collect(),
current: None,
period: f64::from(queue.get_timestamp_period()),
measured: Vec::with_capacity(KEPT),
pool: Readbacks::uncounted(),
})
}
pub(crate) fn begin(&mut self, encoder: &mut wgpu::CommandEncoder) {
self.current = self.slots.iter().position(|s| s.pending.is_none());
if let Some(i) = self.current {
mark(&self.slots[i].set, encoder, Some(0), None);
}
}
pub(crate) fn end(&mut self, encoder: &mut wgpu::CommandEncoder, device: &wgpu::Device) {
let Some(i) = self.current.take() else { return };
let slot = &mut self.slots[i];
mark(&slot.set, encoder, None, Some(1));
encoder.resolve_query_set(&slot.set, 0..2, &slot.resolved, 0);
let waker = std::task::Waker::noop();
slot.pending = Some(Readback::new(&self.pool, device, encoder, &slot.resolved, 0, 16, waker));
}
pub(crate) fn landed(&mut self) -> Option<f32> {
let mut landed = false;
for slot in &mut self.slots {
let Some(bytes) = slot.pending.as_ref().and_then(Readback::take) else { continue };
slot.pending = None;
let Some(ms) = bytes.ok().and_then(|bytes| elapsed_ms(&bytes, self.period)) else { continue };
if self.measured.len() == KEPT {
self.measured.remove(0);
}
self.measured.push(ms);
landed = true;
}
landed.then(|| {
let mut sorted = self.measured.clone();
sorted.sort_by(f32::total_cmp);
sorted[sorted.len() / 2]
})
}
}
fn elapsed_ms(bytes: &[u8], period: f64) -> Option<f32> {
let tick = |i: usize| Some(u64::from_le_bytes(bytes.get(i * 8..i * 8 + 8)?.try_into().ok()?));
let ticks = tick(1)?.checked_sub(tick(0)?)?;
Some((ticks as f64 * period / 1e6) as f32)
}
fn mark(set: &wgpu::QuerySet, encoder: &mut wgpu::CommandEncoder, beginning: Option<u32>, end: Option<u32>) {
encoder.begin_compute_pass(&wgpu::ComputePassDescriptor {
label: Some("latkit timer"),
timestamp_writes: Some(wgpu::ComputePassTimestampWrites {
query_set: set,
beginning_of_pass_write_index: beginning,
end_of_pass_write_index: end,
}),
});
}