phobos 0.10.0

Fast, powerful Vulkan abstraction library
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
//! Abstraction for `VkQueryPool` objects.

use std::ops::Sub;
use std::time::Duration;

use anyhow::{ensure, Result};
use ash::vk;

use crate::{Device, PipelineStage};

/// Trait that must be implemented for each Vulkan query
pub trait Query: Clone + Sized {
    /// The Vulkan query type
    const QUERY_TYPE: vk::QueryType;

    /// Output data from this query
    type Output;

    /// Create a new query object
    fn new(pool: &QueryPoolCreateInfo) -> Self;

    /// The amount of u64 elements in the query
    fn size(&self) -> usize;

    /// Parse this query's data into its output
    fn parse_query(&self, device: &Device, data: &[u64]) -> Self::Output;
}

/// A scoped query is a query that must be queried with `vkCmdBeginQuery` and `vkCmdEndQuery`
pub trait ScopedQuery: Query {}

/// Indicates that this query is an acceleration structure property
pub trait AccelerationStructurePropertyQuery: Query {}

/// A timestamp obtained from a timestamp query
#[derive(Default, Copy, Clone, Debug)]
pub struct Timestamp {
    value: u64,
    // The number of nanoseconds it takes for the timestamp to be incremented
    period: f32,
}

impl Timestamp {
    /// Get the raw value of this timestamp
    pub fn raw_value(&self) -> u64 {
        self.value
    }

    /// Get the number of nanoseconds elapsed since the start of the driver timer
    pub fn nanoseconds(&self) -> u64 {
        (self.value as f64 * self.period as f64) as u64
    }

    /// Get the duration since the start of the driver timer
    pub fn duration_since_epoch(&self) -> Duration {
        Duration::from_nanos(self.nanoseconds())
    }
}

impl Sub<Timestamp> for Timestamp {
    type Output = Duration;

    /// Obtain the difference in duration between two timestamps
    fn sub(self, rhs: Timestamp) -> Self::Output {
        Duration::from_nanos(self.nanoseconds() - rhs.nanoseconds())
    }
}

/// A timestamp query
#[derive(Default, Copy, Clone)]
pub struct TimestampQuery {
    valid_bits: u32,
}

impl Query for TimestampQuery {
    const QUERY_TYPE: vk::QueryType = vk::QueryType::TIMESTAMP;

    type Output = Timestamp;

    fn new(_pool: &QueryPoolCreateInfo) -> Self {
        Self::default()
    }

    fn size(&self) -> usize {
        1
    }

    fn parse_query(&self, device: &Device, data: &[u64]) -> Timestamp {
        // We get one value back
        let value = *data.first().unwrap();
        // Now, clear all the invalid bits
        // Algorithm:
        // Suppose 60 bits are valid. Then the upper 64 - 60 = 4 need to be cleared.
        // We can do this by masking with 00001111 ... 1111
        // To construct this mask, we take the max value (1111 ... 1111),
        // and shift it to the right by the number of
        // invalid bits
        let invalid_bits = u64::BITS - self.valid_bits;
        let mask = u64::MAX >> invalid_bits;
        let value = value & mask;
        Timestamp {
            value,
            period: device.properties().limits.timestamp_period,
        }
    }
}

fn num_queries(flags: vk::QueryPipelineStatisticFlags) -> usize {
    flags.as_raw().count_ones() as usize
}

/// A pipeline statistics query, each field is optional and can be toggled
/// by setting the relevant bit in the query pool create info
#[derive(Debug, Default, Copy, Clone)]
pub struct PipelineStatistics {
    /// Number of vertices in the input assembly stage
    pub input_assembly_vertices: Option<u64>,
    /// Number of primitives in the input assembly stage
    pub input_assembly_primitives: Option<u64>,
    /// Number of vertex shader invocations
    pub vertex_shader_invocations: Option<u64>,
    /// Number of geometry shader invocations
    pub geometry_shader_invocations: Option<u64>,
    /// Number of vertex shader primitives
    pub geometry_shader_primitives: Option<u64>,
    /// Number of clipping stage invocations
    pub clipping_invocations: Option<u64>,
    /// Number of clipping stage primitives
    pub clipping_primitives: Option<u64>,
    /// Number of fragment shader invocations
    pub fragment_shader_invocations: Option<u64>,
    /// Number of patches in the tessellation control shader
    pub tessellation_control_shader_patches: Option<u64>,
    /// Number of tessellation evaluation shader invocations
    pub tessellation_evaluation_shader_invocations: Option<u64>,
    /// Number of compute shader invocations
    pub compute_shader_invocations: Option<u64>,
    /// Number of task shader invocations
    pub task_shader_invocations: Option<u64>,
    /// Number of mesh shader invocations
    pub mesh_shader_invocations: Option<u64>,
}

/// A pipeline statistics query. Each query bit that is requested
/// needs to be individually enabled
#[derive(Default, Copy, Clone)]
pub struct PipelineStatisticsQuery {
    flags: vk::QueryPipelineStatisticFlags,
}

impl PipelineStatisticsQuery {
    fn read_bit(bit: vk::QueryPipelineStatisticFlags, value: u64, output: &mut PipelineStatistics) {
        match bit {
            vk::QueryPipelineStatisticFlags::INPUT_ASSEMBLY_VERTICES => {
                output.input_assembly_vertices = Some(value);
            }
            vk::QueryPipelineStatisticFlags::INPUT_ASSEMBLY_PRIMITIVES => {
                output.input_assembly_primitives = Some(value);
            }
            vk::QueryPipelineStatisticFlags::VERTEX_SHADER_INVOCATIONS => {
                output.vertex_shader_invocations = Some(value);
            }
            vk::QueryPipelineStatisticFlags::GEOMETRY_SHADER_INVOCATIONS => {
                output.geometry_shader_invocations = Some(value);
            }
            vk::QueryPipelineStatisticFlags::GEOMETRY_SHADER_PRIMITIVES => {
                output.geometry_shader_primitives = Some(value);
            }
            vk::QueryPipelineStatisticFlags::CLIPPING_INVOCATIONS => {
                output.clipping_invocations = Some(value);
            }
            vk::QueryPipelineStatisticFlags::CLIPPING_PRIMITIVES => {
                output.clipping_primitives = Some(value);
            }
            vk::QueryPipelineStatisticFlags::FRAGMENT_SHADER_INVOCATIONS => {
                output.fragment_shader_invocations = Some(value);
            }
            vk::QueryPipelineStatisticFlags::TESSELLATION_CONTROL_SHADER_PATCHES => {
                output.tessellation_control_shader_patches = Some(value);
            }
            vk::QueryPipelineStatisticFlags::TESSELLATION_EVALUATION_SHADER_INVOCATIONS => {
                output.tessellation_evaluation_shader_invocations = Some(value);
            }
            vk::QueryPipelineStatisticFlags::COMPUTE_SHADER_INVOCATIONS => {
                output.compute_shader_invocations = Some(value);
            }
            vk::QueryPipelineStatisticFlags::TASK_SHADER_INVOCATIONS_EXT => {
                output.task_shader_invocations = Some(value);
            }
            vk::QueryPipelineStatisticFlags::MESH_SHADER_INVOCATIONS_EXT => {
                output.mesh_shader_invocations = Some(value);
            }
            _ => panic!("Unsupported query pipeline statistic flags"),
        }
    }
}

impl Query for PipelineStatisticsQuery {
    const QUERY_TYPE: vk::QueryType = vk::QueryType::PIPELINE_STATISTICS;
    type Output = PipelineStatistics;

    fn new(pool: &QueryPoolCreateInfo) -> Self {
        Self {
            flags: pool.statistic_flags.unwrap_or_default(),
        }
    }

    fn size(&self) -> usize {
        num_queries(self.flags)
    }

    fn parse_query(&self, _device: &Device, data: &[u64]) -> Self::Output {
        const BITS: [vk::QueryPipelineStatisticFlags; 13] = [
            vk::QueryPipelineStatisticFlags::INPUT_ASSEMBLY_VERTICES,
            vk::QueryPipelineStatisticFlags::INPUT_ASSEMBLY_PRIMITIVES,
            vk::QueryPipelineStatisticFlags::VERTEX_SHADER_INVOCATIONS,
            vk::QueryPipelineStatisticFlags::GEOMETRY_SHADER_INVOCATIONS,
            vk::QueryPipelineStatisticFlags::GEOMETRY_SHADER_PRIMITIVES,
            vk::QueryPipelineStatisticFlags::CLIPPING_INVOCATIONS,
            vk::QueryPipelineStatisticFlags::CLIPPING_PRIMITIVES,
            vk::QueryPipelineStatisticFlags::FRAGMENT_SHADER_INVOCATIONS,
            vk::QueryPipelineStatisticFlags::TESSELLATION_CONTROL_SHADER_PATCHES,
            vk::QueryPipelineStatisticFlags::TESSELLATION_EVALUATION_SHADER_INVOCATIONS,
            vk::QueryPipelineStatisticFlags::COMPUTE_SHADER_INVOCATIONS,
            vk::QueryPipelineStatisticFlags::TASK_SHADER_INVOCATIONS_EXT,
            vk::QueryPipelineStatisticFlags::MESH_SHADER_INVOCATIONS_EXT,
            // Unsupported: VK_QUERY_PIPELINE_STATISTIC_CLUSTER_CULLING_SHADER_INVOCATIONS_BIT_HUAWEI
        ];

        let mut output = PipelineStatistics::default();
        let mut current_index = 0;
        for bit in BITS {
            if self.flags.contains(bit) {
                Self::read_bit(bit, *data.get(current_index).unwrap(), &mut output);
                current_index += 1;
            }
        }

        output
    }
}

impl ScopedQuery for PipelineStatisticsQuery {}

/// Query for the compacted size of an acceleration structure
#[derive(Default, Clone, Copy)]
pub struct AccelerationStructureCompactedSizeQuery;

impl AccelerationStructurePropertyQuery for AccelerationStructureCompactedSizeQuery {}

impl Query for AccelerationStructureCompactedSizeQuery {
    const QUERY_TYPE: vk::QueryType = vk::QueryType::ACCELERATION_STRUCTURE_COMPACTED_SIZE_KHR;
    type Output = u64;

    fn new(_pool: &QueryPoolCreateInfo) -> Self {
        Self::default()
    }

    fn size(&self) -> usize {
        1
    }

    fn parse_query(&self, _device: &Device, data: &[u64]) -> Self::Output {
        *data.first().unwrap()
    }
}

/// Information required to create a query pool
#[derive(Default, Debug, Copy, Clone)]
pub struct QueryPoolCreateInfo {
    /// The number of queries the query pool must reserve memory for
    pub count: u32,
    /// If the query type is [``PipelineStatisticsQuery`], this holds the enabled query bits.
    pub statistic_flags: Option<vk::QueryPipelineStatisticFlags>,
}

/// A Vulkan query pool object. This is generic on any query type that implements the [`Query`] trait.
/// This trait provides information needed to parse the results of the Vulkan query.
pub struct QueryPool<Q: Query> {
    handle: vk::QueryPool,
    device: Device,
    current: u32,
    count: u32,
    queries: Vec<Q>,
}

impl<Q: Query> QueryPool<Q> {
    /// Create a new query pool with at most `count` entries.
    pub fn new(device: Device, info: QueryPoolCreateInfo) -> Result<Self> {
        let vk_info = vk::QueryPoolCreateInfo {
            s_type: vk::StructureType::QUERY_POOL_CREATE_INFO,
            p_next: std::ptr::null(),
            flags: Default::default(),
            query_type: Q::QUERY_TYPE,
            query_count: info.count,
            pipeline_statistics: info.statistic_flags.unwrap_or_default(),
        };

        let handle = unsafe { device.create_query_pool(&vk_info, None)? };
        #[cfg(feature = "log-objects")]
        trace!("Created new VkQueryPool {handle:p}");

        // Every query in the pool must be reset before usage
        unsafe { device.reset_query_pool(handle, 0, info.count) };

        Ok(Self {
            handle,
            device,
            current: 0,
            count: info.count,
            queries: vec![Q::new(&info); info.count as usize],
        })
    }

    /// Advance the query pool to the next query, and return the previous index.
    /// Returns None if the query pool was out of entries.
    pub fn next(&mut self) -> Option<u32> {
        if self.current >= self.count {
            None
        } else {
            let old = self.current;
            self.current += 1;
            Some(old)
        }
    }

    /// Returns the current query pool index. This returns the same value as next() would, except it does not advance the query pool.
    pub fn current(&self) -> u32 {
        if self.current == 0 {
            0
        } else {
            self.current - 1
        }
    }

    /// Wait for a range of results in the query pool
    pub fn wait_for_results(&mut self, first: u32, count: u32) -> Result<Vec<Q::Output>> {
        ensure!(first < self.count, "Query range out of range of query pool");
        ensure!(first + count <= self.count, "Query range out of range of query pool");

        let flags = vk::QueryResultFlags::TYPE_64 | vk::QueryResultFlags::WAIT;
        // Assumption: Every query in the pool has the same number of items, this should always be the case.
        let items_per_query = self
            .queries
            .first()
            .map(|query| query.size())
            .unwrap_or_default();
        let mut buffer = vec![u64::default(); count as usize * items_per_query];
        unsafe {
            self.device.get_query_pool_results(
                self.handle,
                first,
                count,
                buffer.as_mut_slice(),
                flags,
            )?;
        }
        let data = buffer
            .chunks_exact(items_per_query)
            .into_iter()
            .zip(self.queries.iter())
            .map(|(data, query)| query.parse_query(&self.device, data))
            .collect::<Vec<_>>();

        Ok(data)
    }

    /// Wait for results of all queries in the pool
    pub fn wait_for_all_results(&mut self) -> Result<Vec<Q::Output>> {
        self.wait_for_results(0, self.count)
    }

    /// Wait for the result of a single query in the pool
    pub fn wait_for_single_result(&mut self, index: u32) -> Result<Q::Output> {
        ensure!(index < self.count, "Query range out of range of query pool");
        let flags = vk::QueryResultFlags::TYPE_64 | vk::QueryResultFlags::WAIT;
        let query = self.queries.get(index as usize).unwrap();
        let num_items = query.size();
        let mut buffer = vec![u64::default(); num_items];
        unsafe {
            self.device.get_query_pool_results(
                self.handle,
                index,
                1,
                buffer.as_mut_slice(),
                flags,
            )?;
        }
        let data = query.parse_query(&self.device, buffer.as_slice());
        Ok(data)
    }

    /// Reset the query pool
    pub fn reset(&mut self) {
        unsafe { self.device.reset_query_pool(self.handle, 0, self.count) };
        self.current = 0;
    }

    /// Get unsafe access to the underlying `VkQueryPool` handle
    /// # Safety
    /// Modifying this object may put the system in an undefined state
    pub unsafe fn handle(&self) -> vk::QueryPool {
        self.handle
    }
}

impl QueryPool<TimestampQuery> {
    pub(crate) fn write_timestamp(
        &mut self,
        bits: u32,
        cmd: vk::CommandBuffer,
        stage: PipelineStage,
        query: u32,
    ) {
        self.queries.get_mut(query as usize).unwrap().valid_bits = bits;
        unsafe {
            self.device
                .cmd_write_timestamp2(cmd, stage, self.handle, query);
        }
    }
}

impl<Q: Query> Drop for QueryPool<Q> {
    fn drop(&mut self) {
        #[cfg(feature = "log-objects")]
        trace!("Destroying VkQueryPool {:p}", self.handle);

        unsafe {
            self.device.destroy_query_pool(self.handle, None);
        }
    }
}