aboutsummaryrefslogtreecommitdiff
path: root/src/query.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/query.rs')
-rw-r--r--src/query.rs688
1 files changed, 688 insertions, 0 deletions
diff --git a/src/query.rs b/src/query.rs
new file mode 100644
index 0000000..44caada
--- /dev/null
+++ b/src/query.rs
@@ -0,0 +1,688 @@
+// Copyright (c) 2016 The vulkano developers
+// Licensed under the Apache License, Version 2.0
+// <LICENSE-APACHE or
+// https://www.apache.org/licenses/LICENSE-2.0> or the MIT
+// license <LICENSE-MIT or https://opensource.org/licenses/MIT>,
+// at your option. All files in the project carrying such
+// notice may not be copied, modified, or distributed except
+// according to those terms.
+
+//! Gather information about rendering, held in query pools.
+//!
+//! In Vulkan, queries are not created individually. Instead you manipulate **query pools**, which
+//! represent a collection of queries. Whenever you use a query, you have to specify both the query
+//! pool and the slot id within that query pool.
+
+use crate::check_errors;
+use crate::device::Device;
+use crate::device::DeviceOwned;
+use crate::DeviceSize;
+use crate::Error;
+use crate::OomError;
+use crate::Success;
+use crate::VulkanObject;
+use std::error;
+use std::ffi::c_void;
+use std::fmt;
+use std::mem::MaybeUninit;
+use std::ops::Range;
+use std::ptr;
+use std::sync::Arc;
+
+/// A collection of one or more queries of a particular type.
+#[derive(Debug)]
+pub struct QueryPool {
+ pool: ash::vk::QueryPool,
+ device: Arc<Device>,
+ num_slots: u32,
+ ty: QueryType,
+}
+
+impl QueryPool {
+ /// Builds a new query pool.
+ pub fn new(
+ device: Arc<Device>,
+ ty: QueryType,
+ num_slots: u32,
+ ) -> Result<QueryPool, QueryPoolCreationError> {
+ let statistics = match ty {
+ QueryType::PipelineStatistics(flags) => {
+ if !device.enabled_features().pipeline_statistics_query {
+ return Err(QueryPoolCreationError::PipelineStatisticsQueryFeatureNotEnabled);
+ }
+
+ flags.into()
+ }
+ QueryType::Occlusion | QueryType::Timestamp => {
+ ash::vk::QueryPipelineStatisticFlags::empty()
+ }
+ };
+
+ let pool = unsafe {
+ let infos = ash::vk::QueryPoolCreateInfo {
+ flags: ash::vk::QueryPoolCreateFlags::empty(),
+ query_type: ty.into(),
+ query_count: num_slots,
+ pipeline_statistics: statistics,
+ ..Default::default()
+ };
+
+ let mut output = MaybeUninit::uninit();
+ let fns = device.fns();
+ check_errors(fns.v1_0.create_query_pool(
+ device.internal_object(),
+ &infos,
+ ptr::null(),
+ output.as_mut_ptr(),
+ ))?;
+ output.assume_init()
+ };
+
+ Ok(QueryPool {
+ pool,
+ device,
+ num_slots,
+ ty,
+ })
+ }
+
+ /// Returns the [`QueryType`] that this query pool was created with.
+ #[inline]
+ pub fn ty(&self) -> QueryType {
+ self.ty
+ }
+
+ /// Returns the number of query slots of this query pool.
+ #[inline]
+ pub fn num_slots(&self) -> u32 {
+ self.num_slots
+ }
+
+ /// Returns a reference to a single query slot, or `None` if the index is out of range.
+ #[inline]
+ pub fn query(&self, index: u32) -> Option<Query> {
+ if index < self.num_slots() {
+ Some(Query { pool: self, index })
+ } else {
+ None
+ }
+ }
+
+ /// Returns a reference to a range of queries, or `None` if out of range.
+ ///
+ /// # Panic
+ ///
+ /// Panics if the range is empty.
+ #[inline]
+ pub fn queries_range(&self, range: Range<u32>) -> Option<QueriesRange> {
+ assert!(!range.is_empty());
+
+ if range.end <= self.num_slots() {
+ Some(QueriesRange { pool: self, range })
+ } else {
+ None
+ }
+ }
+}
+
+unsafe impl VulkanObject for QueryPool {
+ type Object = ash::vk::QueryPool;
+
+ #[inline]
+ fn internal_object(&self) -> ash::vk::QueryPool {
+ self.pool
+ }
+}
+
+unsafe impl DeviceOwned for QueryPool {
+ #[inline]
+ fn device(&self) -> &Arc<Device> {
+ &self.device
+ }
+}
+
+impl Drop for QueryPool {
+ #[inline]
+ fn drop(&mut self) {
+ unsafe {
+ let fns = self.device.fns();
+ fns.v1_0
+ .destroy_query_pool(self.device.internal_object(), self.pool, ptr::null());
+ }
+ }
+}
+
+/// Error that can happen when creating a query pool.
+#[derive(Clone, Debug, PartialEq, Eq)]
+pub enum QueryPoolCreationError {
+ /// Not enough memory.
+ OomError(OomError),
+ /// A pipeline statistics pool was requested but the corresponding feature wasn't enabled.
+ PipelineStatisticsQueryFeatureNotEnabled,
+}
+
+impl error::Error for QueryPoolCreationError {
+ #[inline]
+ fn source(&self) -> Option<&(dyn error::Error + 'static)> {
+ match *self {
+ QueryPoolCreationError::OomError(ref err) => Some(err),
+ _ => None,
+ }
+ }
+}
+
+impl fmt::Display for QueryPoolCreationError {
+ #[inline]
+ fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
+ write!(
+ fmt,
+ "{}",
+ match *self {
+ QueryPoolCreationError::OomError(_) => "not enough memory available",
+ QueryPoolCreationError::PipelineStatisticsQueryFeatureNotEnabled => {
+ "a pipeline statistics pool was requested but the corresponding feature \
+ wasn't enabled"
+ }
+ }
+ )
+ }
+}
+
+impl From<OomError> for QueryPoolCreationError {
+ #[inline]
+ fn from(err: OomError) -> QueryPoolCreationError {
+ QueryPoolCreationError::OomError(err)
+ }
+}
+
+impl From<Error> for QueryPoolCreationError {
+ #[inline]
+ fn from(err: Error) -> QueryPoolCreationError {
+ match err {
+ err @ Error::OutOfHostMemory => QueryPoolCreationError::OomError(OomError::from(err)),
+ err @ Error::OutOfDeviceMemory => QueryPoolCreationError::OomError(OomError::from(err)),
+ _ => panic!("unexpected error: {:?}", err),
+ }
+ }
+}
+
+/// A reference to a single query slot.
+///
+/// This is created through [`QueryPool::query`].
+#[derive(Clone, Debug)]
+pub struct Query<'a> {
+ pool: &'a QueryPool,
+ index: u32,
+}
+
+impl<'a> Query<'a> {
+ /// Returns a reference to the query pool.
+ #[inline]
+ pub fn pool(&self) -> &'a QueryPool {
+ &self.pool
+ }
+
+ /// Returns the index of the query represented.
+ #[inline]
+ pub fn index(&self) -> u32 {
+ self.index
+ }
+}
+
+/// A reference to a range of queries.
+///
+/// This is created through [`QueryPool::queries_range`].
+#[derive(Clone, Debug)]
+pub struct QueriesRange<'a> {
+ pool: &'a QueryPool,
+ range: Range<u32>,
+}
+
+impl<'a> QueriesRange<'a> {
+ /// Returns a reference to the query pool.
+ #[inline]
+ pub fn pool(&self) -> &'a QueryPool {
+ &self.pool
+ }
+
+ /// Returns the range of queries represented.
+ #[inline]
+ pub fn range(&self) -> Range<u32> {
+ self.range.clone()
+ }
+
+ /// Copies the results of this range of queries to a buffer on the CPU.
+ ///
+ /// [`self.pool().ty().result_size()`](QueryType::result_size) elements
+ /// will be written for each query in the range, plus 1 extra element per query if
+ /// [`QueryResultFlags::with_availability`] is enabled.
+ /// The provided buffer must be large enough to hold the data.
+ ///
+ /// `true` is returned if every result was available and written to the buffer. `false`
+ /// is returned if some results were not yet available; these will not be written to the buffer.
+ ///
+ /// See also [`copy_query_pool_results`](crate::command_buffer::AutoCommandBufferBuilder::copy_query_pool_results).
+ pub fn get_results<T>(
+ &self,
+ destination: &mut [T],
+ flags: QueryResultFlags,
+ ) -> Result<bool, GetResultsError>
+ where
+ T: QueryResultElement,
+ {
+ let stride = self.check_query_pool_results::<T>(
+ destination.as_ptr() as DeviceSize,
+ destination.len() as DeviceSize,
+ flags,
+ )?;
+
+ let result = unsafe {
+ let fns = self.pool.device.fns();
+ check_errors(fns.v1_0.get_query_pool_results(
+ self.pool.device.internal_object(),
+ self.pool.internal_object(),
+ self.range.start,
+ self.range.end - self.range.start,
+ std::mem::size_of_val(destination),
+ destination.as_mut_ptr() as *mut c_void,
+ stride,
+ ash::vk::QueryResultFlags::from(flags) | T::FLAG,
+ ))?
+ };
+
+ Ok(match result {
+ Success::Success => true,
+ Success::NotReady => false,
+ s => panic!("unexpected success value: {:?}", s),
+ })
+ }
+
+ pub(crate) fn check_query_pool_results<T>(
+ &self,
+ buffer_start: DeviceSize,
+ buffer_len: DeviceSize,
+ flags: QueryResultFlags,
+ ) -> Result<DeviceSize, GetResultsError>
+ where
+ T: QueryResultElement,
+ {
+ assert!(buffer_len > 0);
+ debug_assert!(buffer_start % std::mem::size_of::<T>() as DeviceSize == 0);
+
+ let count = self.range.end - self.range.start;
+ let per_query_len = self.pool.ty.result_size() + flags.with_availability as DeviceSize;
+ let required_len = per_query_len * count as DeviceSize;
+
+ if buffer_len < required_len {
+ return Err(GetResultsError::BufferTooSmall {
+ required_len: required_len as DeviceSize,
+ actual_len: buffer_len as DeviceSize,
+ });
+ }
+
+ match self.pool.ty {
+ QueryType::Occlusion => (),
+ QueryType::PipelineStatistics(_) => (),
+ QueryType::Timestamp => {
+ if flags.partial {
+ return Err(GetResultsError::InvalidFlags);
+ }
+ }
+ }
+
+ Ok(per_query_len * std::mem::size_of::<T>() as DeviceSize)
+ }
+}
+
+/// Error that can happen when calling [`QueriesRange::get_results`].
+#[derive(Clone, Debug, PartialEq, Eq)]
+pub enum GetResultsError {
+ /// The buffer is too small for the operation.
+ BufferTooSmall {
+ /// Required number of elements in the buffer.
+ required_len: DeviceSize,
+ /// Actual number of elements in the buffer.
+ actual_len: DeviceSize,
+ },
+ /// The connection to the device has been lost.
+ DeviceLost,
+ /// The provided flags are not allowed for this type of query.
+ InvalidFlags,
+ /// Not enough memory.
+ OomError(OomError),
+}
+
+impl From<Error> for GetResultsError {
+ #[inline]
+ fn from(err: Error) -> Self {
+ match err {
+ Error::OutOfHostMemory | Error::OutOfDeviceMemory => {
+ Self::OomError(OomError::from(err))
+ }
+ Error::DeviceLost => Self::DeviceLost,
+ _ => panic!("unexpected error: {:?}", err),
+ }
+ }
+}
+
+impl From<OomError> for GetResultsError {
+ #[inline]
+ fn from(err: OomError) -> Self {
+ Self::OomError(err)
+ }
+}
+
+impl fmt::Display for GetResultsError {
+ #[inline]
+ fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
+ write!(
+ fmt,
+ "{}",
+ match *self {
+ Self::BufferTooSmall { .. } => {
+ "the buffer is too small for the operation"
+ }
+ Self::DeviceLost => "the connection to the device has been lost",
+ Self::InvalidFlags => {
+ "the provided flags are not allowed for this type of query"
+ }
+ Self::OomError(_) => "not enough memory available",
+ }
+ )
+ }
+}
+
+impl error::Error for GetResultsError {
+ #[inline]
+ fn source(&self) -> Option<&(dyn error::Error + 'static)> {
+ match *self {
+ Self::OomError(ref err) => Some(err),
+ _ => None,
+ }
+ }
+}
+
+/// A trait for elements of buffers that can be used as a destination for query results.
+///
+/// # Safety
+/// This is implemented for `u32` and `u64`. Unless you really know what you're doing, you should
+/// not implement this trait for any other type.
+pub unsafe trait QueryResultElement {
+ const FLAG: ash::vk::QueryResultFlags;
+}
+
+unsafe impl QueryResultElement for u32 {
+ const FLAG: ash::vk::QueryResultFlags = ash::vk::QueryResultFlags::empty();
+}
+
+unsafe impl QueryResultElement for u64 {
+ const FLAG: ash::vk::QueryResultFlags = ash::vk::QueryResultFlags::TYPE_64;
+}
+
+/// The type of query that a query pool should perform.
+#[derive(Debug, Copy, Clone)]
+pub enum QueryType {
+ /// Tracks the number of samples that pass per-fragment tests (e.g. the depth test).
+ Occlusion,
+ /// Tracks statistics on pipeline invocations and their input data.
+ PipelineStatistics(QueryPipelineStatisticFlags),
+ /// Writes timestamps at chosen points in a command buffer.
+ Timestamp,
+}
+
+impl QueryType {
+ /// Returns the number of [`QueryResultElement`]s that are needed to hold the result of a
+ /// single query of this type.
+ ///
+ /// - For `Occlusion` and `Timestamp` queries, this returns 1.
+ /// - For `PipelineStatistics` queries, this returns the number of statistics flags enabled.
+ ///
+ /// If the results are retrieved with [`QueryResultFlags::with_availability`] enabled, then
+ /// an additional element is required per query.
+ #[inline]
+ pub const fn result_size(&self) -> DeviceSize {
+ match self {
+ Self::Occlusion | Self::Timestamp => 1,
+ Self::PipelineStatistics(flags) => flags.count(),
+ }
+ }
+}
+
+impl From<QueryType> for ash::vk::QueryType {
+ #[inline]
+ fn from(value: QueryType) -> Self {
+ match value {
+ QueryType::Occlusion => ash::vk::QueryType::OCCLUSION,
+ QueryType::PipelineStatistics(_) => ash::vk::QueryType::PIPELINE_STATISTICS,
+ QueryType::Timestamp => ash::vk::QueryType::TIMESTAMP,
+ }
+ }
+}
+
+/// Flags that control how a query is to be executed.
+#[derive(Clone, Copy, Debug, Default)]
+pub struct QueryControlFlags {
+ /// For occlusion queries, specifies that the result must reflect the exact number of
+ /// tests passed. If not enabled, the query may return a result of 1 even if more fragments
+ /// passed the test.
+ pub precise: bool,
+}
+
+impl From<QueryControlFlags> for ash::vk::QueryControlFlags {
+ #[inline]
+ fn from(value: QueryControlFlags) -> Self {
+ let mut result = ash::vk::QueryControlFlags::empty();
+ if value.precise {
+ result |= ash::vk::QueryControlFlags::PRECISE;
+ }
+ result
+ }
+}
+
+/// For pipeline statistics queries, the statistics that should be gathered.
+#[derive(Clone, Copy, Debug, Default)]
+pub struct QueryPipelineStatisticFlags {
+ /// Count the number of vertices processed by the input assembly.
+ pub input_assembly_vertices: bool,
+ /// Count the number of primitives processed by the input assembly.
+ pub input_assembly_primitives: bool,
+ /// Count the number of times a vertex shader is invoked.
+ pub vertex_shader_invocations: bool,
+ /// Count the number of times a geometry shader is invoked.
+ pub geometry_shader_invocations: bool,
+ /// Count the number of primitives generated by geometry shaders.
+ pub geometry_shader_primitives: bool,
+ /// Count the number of times the clipping stage is invoked on a primitive.
+ pub clipping_invocations: bool,
+ /// Count the number of primitives that are output by the clipping stage.
+ pub clipping_primitives: bool,
+ /// Count the number of times a fragment shader is invoked.
+ pub fragment_shader_invocations: bool,
+ /// Count the number of patches processed by a tessellation control shader.
+ pub tessellation_control_shader_patches: bool,
+ /// Count the number of times a tessellation evaluation shader is invoked.
+ pub tessellation_evaluation_shader_invocations: bool,
+ /// Count the number of times a compute shader is invoked.
+ pub compute_shader_invocations: bool,
+}
+
+impl QueryPipelineStatisticFlags {
+ #[inline]
+ pub fn none() -> QueryPipelineStatisticFlags {
+ QueryPipelineStatisticFlags {
+ input_assembly_vertices: false,
+ input_assembly_primitives: false,
+ vertex_shader_invocations: false,
+ geometry_shader_invocations: false,
+ geometry_shader_primitives: false,
+ clipping_invocations: false,
+ clipping_primitives: false,
+ fragment_shader_invocations: false,
+ tessellation_control_shader_patches: false,
+ tessellation_evaluation_shader_invocations: false,
+ compute_shader_invocations: false,
+ }
+ }
+
+ /// Returns the number of flags that are set to `true`.
+ #[inline]
+ pub const fn count(&self) -> DeviceSize {
+ let &Self {
+ input_assembly_vertices,
+ input_assembly_primitives,
+ vertex_shader_invocations,
+ geometry_shader_invocations,
+ geometry_shader_primitives,
+ clipping_invocations,
+ clipping_primitives,
+ fragment_shader_invocations,
+ tessellation_control_shader_patches,
+ tessellation_evaluation_shader_invocations,
+ compute_shader_invocations,
+ } = self;
+ input_assembly_vertices as DeviceSize
+ + input_assembly_primitives as DeviceSize
+ + vertex_shader_invocations as DeviceSize
+ + geometry_shader_invocations as DeviceSize
+ + geometry_shader_primitives as DeviceSize
+ + clipping_invocations as DeviceSize
+ + clipping_primitives as DeviceSize
+ + fragment_shader_invocations as DeviceSize
+ + tessellation_control_shader_patches as DeviceSize
+ + tessellation_evaluation_shader_invocations as DeviceSize
+ + compute_shader_invocations as DeviceSize
+ }
+
+ /// Returns `true` if any flags referring to compute operations are set to `true`.
+ #[inline]
+ pub const fn is_compute(&self) -> bool {
+ let &Self {
+ compute_shader_invocations,
+ ..
+ } = self;
+ compute_shader_invocations
+ }
+
+ /// Returns `true` if any flags referring to graphics operations are set to `true`.
+ #[inline]
+ pub const fn is_graphics(&self) -> bool {
+ let &Self {
+ input_assembly_vertices,
+ input_assembly_primitives,
+ vertex_shader_invocations,
+ geometry_shader_invocations,
+ geometry_shader_primitives,
+ clipping_invocations,
+ clipping_primitives,
+ fragment_shader_invocations,
+ tessellation_control_shader_patches,
+ tessellation_evaluation_shader_invocations,
+ ..
+ } = self;
+ input_assembly_vertices
+ || input_assembly_primitives
+ || vertex_shader_invocations
+ || geometry_shader_invocations
+ || geometry_shader_primitives
+ || clipping_invocations
+ || clipping_primitives
+ || fragment_shader_invocations
+ || tessellation_control_shader_patches
+ || tessellation_evaluation_shader_invocations
+ }
+}
+
+impl From<QueryPipelineStatisticFlags> for ash::vk::QueryPipelineStatisticFlags {
+ fn from(value: QueryPipelineStatisticFlags) -> ash::vk::QueryPipelineStatisticFlags {
+ let mut result = ash::vk::QueryPipelineStatisticFlags::empty();
+ if value.input_assembly_vertices {
+ result |= ash::vk::QueryPipelineStatisticFlags::INPUT_ASSEMBLY_VERTICES;
+ }
+ if value.input_assembly_primitives {
+ result |= ash::vk::QueryPipelineStatisticFlags::INPUT_ASSEMBLY_PRIMITIVES;
+ }
+ if value.vertex_shader_invocations {
+ result |= ash::vk::QueryPipelineStatisticFlags::VERTEX_SHADER_INVOCATIONS;
+ }
+ if value.geometry_shader_invocations {
+ result |= ash::vk::QueryPipelineStatisticFlags::GEOMETRY_SHADER_INVOCATIONS;
+ }
+ if value.geometry_shader_primitives {
+ result |= ash::vk::QueryPipelineStatisticFlags::GEOMETRY_SHADER_PRIMITIVES;
+ }
+ if value.clipping_invocations {
+ result |= ash::vk::QueryPipelineStatisticFlags::CLIPPING_INVOCATIONS;
+ }
+ if value.clipping_primitives {
+ result |= ash::vk::QueryPipelineStatisticFlags::CLIPPING_PRIMITIVES;
+ }
+ if value.fragment_shader_invocations {
+ result |= ash::vk::QueryPipelineStatisticFlags::FRAGMENT_SHADER_INVOCATIONS;
+ }
+ if value.tessellation_control_shader_patches {
+ result |= ash::vk::QueryPipelineStatisticFlags::TESSELLATION_CONTROL_SHADER_PATCHES;
+ }
+ if value.tessellation_evaluation_shader_invocations {
+ result |=
+ ash::vk::QueryPipelineStatisticFlags::TESSELLATION_EVALUATION_SHADER_INVOCATIONS;
+ }
+ if value.compute_shader_invocations {
+ result |= ash::vk::QueryPipelineStatisticFlags::COMPUTE_SHADER_INVOCATIONS;
+ }
+ result
+ }
+}
+
+/// Flags to control how the results of a query should be retrieved.
+///
+/// `VK_QUERY_RESULT_64_BIT` is not included, as it is determined automatically via the
+/// [`QueryResultElement`] trait.
+#[derive(Clone, Copy, Debug, Default)]
+pub struct QueryResultFlags {
+ /// Wait for the results to become available before writing the results.
+ pub wait: bool,
+ /// Write an additional element to the end of each query's results, indicating the availability
+ /// of the results:
+ /// - Nonzero: The results are available, and have been written to the element(s) preceding.
+ /// - Zero: The results are not yet available, and have not been written.
+ pub with_availability: bool,
+ /// Allow writing partial results to the buffer, instead of waiting until they are fully
+ /// available.
+ pub partial: bool,
+}
+
+impl From<QueryResultFlags> for ash::vk::QueryResultFlags {
+ #[inline]
+ fn from(value: QueryResultFlags) -> Self {
+ let mut result = ash::vk::QueryResultFlags::empty();
+ if value.wait {
+ result |= ash::vk::QueryResultFlags::WAIT;
+ }
+ if value.with_availability {
+ result |= ash::vk::QueryResultFlags::WITH_AVAILABILITY;
+ }
+ if value.partial {
+ result |= ash::vk::QueryResultFlags::PARTIAL;
+ }
+ result
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::query::QueryPipelineStatisticFlags;
+ use crate::query::QueryPool;
+ use crate::query::QueryPoolCreationError;
+ use crate::query::QueryType;
+
+ #[test]
+ fn pipeline_statistics_feature() {
+ let (device, _) = gfx_dev_and_queue!();
+
+ let ty = QueryType::PipelineStatistics(QueryPipelineStatisticFlags::none());
+ match QueryPool::new(device, ty, 256) {
+ Err(QueryPoolCreationError::PipelineStatisticsQueryFeatureNotEnabled) => (),
+ _ => panic!(),
+ };
+ }
+}