Skip to main content

loam_render/
gpu_timer.rs

1//! wgpu rejects `MAP_READ | QUERY_RESOLVE` on one buffer and locks a whole
2//! buffer while any slice is mapped, so the resolve buffer copies into a map buffer.
3
4use 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
16/// Sixteen sections per frame; a result arrives a frame late, and a frame whose map is still in flight measures nothing.
17pub 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    /// The last mapped result for `slot` while its section keeps the same name.
93    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}