Skip to main content

ursus_core/vulkan/
timestamps.rs

1use ash::vk;
2
3#[derive(Debug, Clone, Default)]
4pub struct GpuFrameTimes {
5    pub passes: Vec<(String, f32)>,
6    pub total_ms: f32,
7}
8
9struct FramePool {
10    pool: vk::QueryPool,
11    submitted_count: u32,
12}
13
14pub struct GpuTimestampPool {
15    pools: Vec<FramePool>,
16    pass_names: Vec<String>,
17    timestamp_period: f32,
18    query_count: u32,
19    device: ash::Device,
20    pub last_frame: GpuFrameTimes,
21}
22
23impl GpuTimestampPool {
24    pub fn new(
25        device: &ash::Device,
26        physical_device: vk::PhysicalDevice,
27        instance: &ash::Instance,
28        frames_in_flight: u32,
29        pass_names: Vec<String>,
30        command_pool: vk::CommandPool,
31        queue: vk::Queue,
32    ) -> anyhow::Result<Self> {
33        let props = unsafe { instance.get_physical_device_properties(physical_device) };
34        let timestamp_period = props.limits.timestamp_period;
35        if timestamp_period == 0.0 {
36            anyhow::bail!("GPU does not support timestamp queries");
37        }
38
39        let query_count = (pass_names.len() * 2) as u32;
40
41        let mut raw_pools = Vec::with_capacity(frames_in_flight as usize);
42        for _ in 0..frames_in_flight {
43            let pool = unsafe {
44                device.create_query_pool(
45                    &vk::QueryPoolCreateInfo::default().query_type(vk::QueryType::TIMESTAMP).query_count(query_count),
46                    None,
47                )?
48            };
49            raw_pools.push(pool);
50        }
51
52        initial_reset_all(device, command_pool, queue, &raw_pools, query_count)?;
53
54        let pools = raw_pools.into_iter().map(|pool| FramePool { pool, submitted_count: 0 }).collect();
55
56        log::debug!("GpuTimestampPool: {} frames x {} passes x 2 queries", frames_in_flight, pass_names.len());
57
58        Ok(Self {
59            pools,
60            pass_names,
61            timestamp_period,
62            query_count,
63            device: device.clone(),
64            last_frame: GpuFrameTimes::default(),
65        })
66    }
67
68    pub fn read_and_reset(&mut self, frame_index: usize, cmd: vk::CommandBuffer) {
69        let fp = &mut self.pools[frame_index];
70
71        if fp.submitted_count > 0 {
72            let mut raw = vec![0u64; self.query_count as usize];
73            match unsafe { self.device.get_query_pool_results(fp.pool, 0, &mut raw, vk::QueryResultFlags::TYPE_64) } {
74                Ok(()) => {
75                    let period_ms = self.timestamp_period * 1e-6;
76                    let mut passes = Vec::with_capacity(self.pass_names.len());
77                    let mut total = 0.0f32;
78
79                    for (i, name) in self.pass_names.iter().enumerate() {
80                        let begin = raw[i * 2];
81                        let end = raw[i * 2 + 1];
82                        let ms = if end > begin {
83                            (end - begin) as f32 * period_ms
84                        } else {
85                            0.0
86                        };
87                        passes.push((name.clone(), ms));
88                        total += ms;
89                    }
90
91                    self.last_frame = GpuFrameTimes { passes, total_ms: total };
92                }
93                Err(vk::Result::NOT_READY) => {}
94                Err(e) => log::warn!("GpuTimestampPool read failed: {:?}", e),
95            }
96        }
97
98        unsafe {
99            self.device.cmd_reset_query_pool(cmd, fp.pool, 0, self.query_count);
100        }
101    }
102
103    pub fn begin_pass(&self, cmd: vk::CommandBuffer, frame_index: usize, pass_index: usize) {
104        unsafe {
105            self.device.cmd_write_timestamp2(
106                cmd,
107                vk::PipelineStageFlags2::TOP_OF_PIPE,
108                self.pools[frame_index].pool,
109                (pass_index * 2) as u32,
110            );
111        }
112    }
113
114    pub fn end_pass(&self, cmd: vk::CommandBuffer, frame_index: usize, pass_index: usize) {
115        unsafe {
116            self.device.cmd_write_timestamp2(
117                cmd,
118                vk::PipelineStageFlags2::BOTTOM_OF_PIPE,
119                self.pools[frame_index].pool,
120                (pass_index * 2 + 1) as u32,
121            );
122        }
123    }
124
125    pub fn mark_submitted(&mut self, frame_index: usize) {
126        self.pools[frame_index].submitted_count += 1;
127    }
128}
129
130impl Drop for GpuTimestampPool {
131    fn drop(&mut self) {
132        for fp in &self.pools {
133            unsafe { self.device.destroy_query_pool(fp.pool, None) };
134        }
135    }
136}
137
138fn initial_reset_all(
139    device: &ash::Device,
140    command_pool: vk::CommandPool,
141    queue: vk::Queue,
142    pools: &[vk::QueryPool],
143    query_count: u32,
144) -> anyhow::Result<()> {
145    let cmd = unsafe {
146        device.allocate_command_buffers(
147            &vk::CommandBufferAllocateInfo::default()
148                .command_pool(command_pool)
149                .level(vk::CommandBufferLevel::PRIMARY)
150                .command_buffer_count(1),
151        )?[0]
152    };
153    unsafe {
154        device.begin_command_buffer(
155            cmd,
156            &vk::CommandBufferBeginInfo::default().flags(vk::CommandBufferUsageFlags::ONE_TIME_SUBMIT),
157        )?;
158        for &pool in pools {
159            device.cmd_reset_query_pool(cmd, pool, 0, query_count);
160        }
161        device.end_command_buffer(cmd)?;
162        device.queue_submit(
163            queue,
164            &[vk::SubmitInfo::default().command_buffers(std::slice::from_ref(&cmd))],
165            vk::Fence::null(),
166        )?;
167        device.queue_wait_idle(queue)?;
168        device.free_command_buffers(command_pool, &[cmd]);
169    }
170    Ok(())
171}