ursus_core/vulkan/
timestamps.rs1use 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}