1use std::sync::atomic::{AtomicBool, Ordering};
5use std::sync::{Arc, Mutex};
6use std::time::Duration;
7use wgpu::{
8 Buffer, BufferDescriptor, BufferUsages, CommandEncoder, Device, Features, MapMode, QuerySet,
9 QuerySetDescriptor, QueryType, Queue,
10};
11
12const MAX_SECTIONS: usize = 16;
13
14const SECTION_BYTES: u64 = 16;
15
16pub struct SectionTimer {
18 query_set: QuerySet,
19 resolve_buffer: Buffer,
20 map_buffer: Buffer,
21 timestamp_period_ns: f32,
22 open: usize,
23 resolved: Option<usize>,
24 names: [&'static str; MAX_SECTIONS],
25 in_flight: Arc<AtomicBool>,
26 last: Arc<Mutex<SectionResults>>,
27}
28
29struct SectionResults {
30 names: [&'static str; MAX_SECTIONS],
31 elapsed: [Duration; MAX_SECTIONS],
32}
33
34impl SectionTimer {
35 pub fn new(device: &Device, queue: &Queue) -> Option<Self> {
36 let needed = Features::TIMESTAMP_QUERY | Features::TIMESTAMP_QUERY_INSIDE_ENCODERS;
37 if !device.features().contains(needed) {
38 return None;
39 }
40 let query_set = device.create_query_set(&QuerySetDescriptor {
41 label: Some("loam-render::SectionTimer::query_set"),
42 ty: QueryType::Timestamp,
43 count: (MAX_SECTIONS * 2) as u32,
44 });
45 let resolve_buffer = device.create_buffer(&BufferDescriptor {
46 label: Some("loam-render::SectionTimer::resolve_buffer"),
47 size: SECTION_BYTES * MAX_SECTIONS as u64,
48 usage: BufferUsages::QUERY_RESOLVE | BufferUsages::COPY_SRC,
49 mapped_at_creation: false,
50 });
51 let map_buffer = device.create_buffer(&BufferDescriptor {
52 label: Some("loam-render::SectionTimer::map_buffer"),
53 size: SECTION_BYTES * MAX_SECTIONS as u64,
54 usage: BufferUsages::MAP_READ | BufferUsages::COPY_DST,
55 mapped_at_creation: false,
56 });
57 Some(Self {
58 query_set,
59 resolve_buffer,
60 map_buffer,
61 timestamp_period_ns: queue.get_timestamp_period(),
62 open: 0,
63 resolved: None,
64 names: [""; MAX_SECTIONS],
65 in_flight: Arc::new(AtomicBool::new(false)),
66 last: Arc::new(Mutex::new(SectionResults {
67 names: [""; MAX_SECTIONS],
68 elapsed: [Duration::ZERO; MAX_SECTIONS],
69 })),
70 })
71 }
72
73 pub fn begin_frame(&mut self) {
74 self.open = 0;
75 }
76
77 pub fn open(&mut self, encoder: &mut CommandEncoder, name: &'static str) -> Option<usize> {
78 if self.open >= MAX_SECTIONS {
79 return None;
80 }
81 let slot = self.open;
82 encoder.write_timestamp(&self.query_set, (slot * 2) as u32);
83 self.names[slot] = name;
84 self.open += 1;
85 Some(slot)
86 }
87
88 pub fn close(&mut self, encoder: &mut CommandEncoder, slot: usize) {
89 encoder.write_timestamp(&self.query_set, (slot * 2 + 1) as u32);
90 }
91
92 pub fn elapsed(&self, slot: usize) -> Option<Duration> {
94 let last = self.last.lock().unwrap_or_else(|e| e.into_inner());
95 (last.names[slot] == self.names[slot]).then(|| last.elapsed[slot])
96 }
97
98 pub fn resolve(&mut self, encoder: &mut CommandEncoder) {
99 if self.open == 0 || self.in_flight.load(Ordering::Acquire) {
100 return;
101 }
102 let bytes = SECTION_BYTES * self.open as u64;
103 encoder.resolve_query_set(
104 &self.query_set,
105 0..(self.open * 2) as u32,
106 &self.resolve_buffer,
107 0,
108 );
109 encoder.copy_buffer_to_buffer(&self.resolve_buffer, 0, &self.map_buffer, 0, bytes);
110 self.in_flight.store(true, Ordering::Release);
111 self.resolved = Some(self.open);
112 }
113
114 pub fn after_submit(&mut self) -> bool {
115 let Some(open) = self.resolved.take() else {
116 return false;
117 };
118 let bytes = SECTION_BYTES * open as u64;
119 let names = self.names;
120 let buffer = self.map_buffer.clone();
121 let reader = buffer.clone();
122 let period_ns = self.timestamp_period_ns;
123 let last = self.last.clone();
124 let flag = self.in_flight.clone();
125 buffer
126 .slice(0..bytes)
127 .map_async(MapMode::Read, move |result| {
128 if result.is_ok() {
129 let view = reader.slice(0..bytes).get_mapped_range();
130 let mut done = last.lock().unwrap_or_else(|e| e.into_inner());
131 done.names = [""; MAX_SECTIONS];
132 for (slot, name) in names.iter().enumerate().take(open) {
133 let at = slot * SECTION_BYTES as usize;
134 let (Ok(start), Ok(end)) = (
135 <[u8; 8]>::try_from(&view[at..at + 8]),
136 <[u8; 8]>::try_from(&view[at + 8..at + 16]),
137 ) else {
138 break;
139 };
140 let ticks =
141 u64::from_le_bytes(end).saturating_sub(u64::from_le_bytes(start));
142 let nanos = (ticks as f64 * period_ns as f64) as u64;
143 done.names[slot] = name;
144 done.elapsed[slot] = Duration::from_nanos(nanos);
145 }
146 drop(done);
147 drop(view);
148 reader.unmap();
149 }
150 flag.store(false, Ordering::Release);
151 });
152 true
153 }
154}