blob: bb4b885605323b901827def1c1e84f86da399c6b [file] [log] [blame]
// 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::{
buffer::BufferContents,
device::{Device, DeviceOwned},
macros::{impl_id_counter, vulkan_bitflags},
DeviceSize, OomError, RequirementNotMet, RequiresOneOf, VulkanError, VulkanObject,
};
use std::{
error::Error,
ffi::c_void,
fmt::{Display, Error as FmtError, Formatter},
mem::{size_of_val, MaybeUninit},
num::NonZeroU64,
ops::Range,
ptr,
sync::Arc,
};
/// A collection of one or more queries of a particular type.
#[derive(Debug)]
pub struct QueryPool {
handle: ash::vk::QueryPool,
device: Arc<Device>,
id: NonZeroU64,
query_type: QueryType,
query_count: u32,
}
impl QueryPool {
/// Creates a new `QueryPool`.
///
/// # Panics
///
/// - Panics if `create_info.query_count` is `0`.
pub fn new(
device: Arc<Device>,
create_info: QueryPoolCreateInfo,
) -> Result<Arc<QueryPool>, QueryPoolCreationError> {
let QueryPoolCreateInfo {
query_type,
query_count,
_ne: _,
} = create_info;
// VUID-VkQueryPoolCreateInfo-queryCount-02763
assert!(query_count != 0);
let pipeline_statistics = match query_type {
QueryType::PipelineStatistics(flags) => {
// VUID-VkQueryPoolCreateInfo-queryType-00791
if !device.enabled_features().pipeline_statistics_query {
return Err(QueryPoolCreationError::PipelineStatisticsQueryFeatureNotEnabled);
}
// VUID-VkQueryPoolCreateInfo-queryType-00792
flags.into()
}
QueryType::Occlusion | QueryType::Timestamp => {
ash::vk::QueryPipelineStatisticFlags::empty()
}
};
let create_info = ash::vk::QueryPoolCreateInfo {
flags: ash::vk::QueryPoolCreateFlags::empty(),
query_type: query_type.into(),
query_count,
pipeline_statistics,
..Default::default()
};
let handle = unsafe {
let fns = device.fns();
let mut output = MaybeUninit::uninit();
(fns.v1_0.create_query_pool)(
device.handle(),
&create_info,
ptr::null(),
output.as_mut_ptr(),
)
.result()
.map_err(VulkanError::from)?;
output.assume_init()
};
Ok(Arc::new(QueryPool {
handle,
device,
id: Self::next_id(),
query_type,
query_count,
}))
}
/// Creates a new `QueryPool` from a raw object handle.
///
/// # Safety
///
/// - `handle` must be a valid Vulkan object handle created from `device`.
/// - `create_info` must match the info used to create the object.
#[inline]
pub unsafe fn from_handle(
device: Arc<Device>,
handle: ash::vk::QueryPool,
create_info: QueryPoolCreateInfo,
) -> Arc<QueryPool> {
let QueryPoolCreateInfo {
query_type,
query_count,
_ne: _,
} = create_info;
Arc::new(QueryPool {
handle,
device,
id: Self::next_id(),
query_type,
query_count,
})
}
/// Returns the query type of the pool.
#[inline]
pub fn query_type(&self) -> QueryType {
self.query_type
}
/// Returns the number of query slots of this query pool.
#[inline]
pub fn query_count(&self) -> u32 {
self.query_count
}
/// 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.query_count {
Some(Query { pool: self, index })
} else {
None
}
}
/// Returns a reference to a range of queries, or `None` if out of range.
///
/// # Panics
///
/// - 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.query_count {
Some(QueriesRange { pool: self, range })
} else {
None
}
}
}
impl Drop for QueryPool {
#[inline]
fn drop(&mut self) {
unsafe {
let fns = self.device.fns();
(fns.v1_0.destroy_query_pool)(self.device.handle(), self.handle, ptr::null());
}
}
}
unsafe impl VulkanObject for QueryPool {
type Handle = ash::vk::QueryPool;
#[inline]
fn handle(&self) -> Self::Handle {
self.handle
}
}
unsafe impl DeviceOwned for QueryPool {
#[inline]
fn device(&self) -> &Arc<Device> {
&self.device
}
}
impl_id_counter!(QueryPool);
/// Parameters to create a new `QueryPool`.
#[derive(Clone, Debug)]
pub struct QueryPoolCreateInfo {
/// The type of query that the pool should be for.
///
/// There is no default value.
pub query_type: QueryType,
/// The number of queries to create in the pool.
///
/// The default value is `0`, which must be overridden.
pub query_count: u32,
pub _ne: crate::NonExhaustive,
}
impl QueryPoolCreateInfo {
/// Returns a `QueryPoolCreateInfo` with the specified `query_type`.
#[inline]
pub fn query_type(query_type: QueryType) -> Self {
Self {
query_type,
query_count: 0,
_ne: crate::NonExhaustive(()),
}
}
}
/// 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 for QueryPoolCreationError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
match self {
QueryPoolCreationError::OomError(err) => Some(err),
_ => None,
}
}
}
impl Display for QueryPoolCreationError {
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), FmtError> {
write!(
f,
"{}",
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 {
fn from(err: OomError) -> QueryPoolCreationError {
QueryPoolCreationError::OomError(err)
}
}
impl From<VulkanError> for QueryPoolCreationError {
fn from(err: VulkanError) -> QueryPoolCreationError {
match err {
err @ VulkanError::OutOfHostMemory => {
QueryPoolCreationError::OomError(OomError::from(err))
}
err @ VulkanError::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_len()`] will be written for each query in the range, plus 1 extra
/// element per query if [`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`].
///
/// [`self.pool().ty().result_len()`]: QueryType::result_len
/// [`WITH_AVAILABILITY`]: QueryResultFlags::WITH_AVAILABILITY
/// [`copy_query_pool_results`]: crate::command_buffer::AutoCommandBufferBuilder::copy_query_pool_results
#[inline]
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();
(fns.v1_0.get_query_pool_results)(
self.pool.device.handle(),
self.pool.handle(),
self.range.start,
self.range.end - self.range.start,
size_of_val(destination),
destination.as_mut_ptr() as *mut c_void,
stride,
ash::vk::QueryResultFlags::from(flags) | T::FLAG,
)
};
match result {
ash::vk::Result::SUCCESS => Ok(true),
ash::vk::Result::NOT_READY => Ok(false),
err => Err(VulkanError::from(err).into()),
}
}
pub(crate) fn check_query_pool_results<T>(
&self,
buffer_start: DeviceSize,
buffer_len: DeviceSize,
flags: QueryResultFlags,
) -> Result<DeviceSize, GetResultsError>
where
T: QueryResultElement,
{
// VUID-vkGetQueryPoolResults-flags-parameter
// VUID-vkCmdCopyQueryPoolResults-flags-parameter
flags.validate_device(&self.pool.device)?;
assert!(buffer_len > 0);
// VUID-vkGetQueryPoolResults-flags-02828
// VUID-vkGetQueryPoolResults-flags-00815
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.query_type.result_len()
+ flags.intersects(QueryResultFlags::WITH_AVAILABILITY) as DeviceSize;
let required_len = per_query_len * count as DeviceSize;
// VUID-vkGetQueryPoolResults-dataSize-00817
if buffer_len < required_len {
return Err(GetResultsError::BufferTooSmall {
required_len: required_len as DeviceSize,
actual_len: buffer_len as DeviceSize,
});
}
match self.pool.query_type {
QueryType::Occlusion => (),
QueryType::PipelineStatistics(_) => (),
QueryType::Timestamp => {
// VUID-vkGetQueryPoolResults-queryType-00818
if flags.intersects(QueryResultFlags::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 connection to the device has been lost.
DeviceLost,
/// Not enough memory.
OomError(OomError),
RequirementNotMet {
required_for: &'static str,
requires_one_of: RequiresOneOf,
},
/// 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 provided flags are not allowed for this type of query.
InvalidFlags,
}
impl Error for GetResultsError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
match self {
Self::OomError(err) => Some(err),
_ => None,
}
}
}
impl Display for GetResultsError {
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), FmtError> {
match self {
Self::OomError(_) => write!(f, "not enough memory available"),
Self::DeviceLost => write!(f, "the connection to the device has been lost"),
Self::RequirementNotMet {
required_for,
requires_one_of,
} => write!(
f,
"a requirement was not met for: {}; requires one of: {}",
required_for, requires_one_of,
),
Self::BufferTooSmall { .. } => write!(f, "the buffer is too small for the operation"),
Self::InvalidFlags => write!(
f,
"the provided flags are not allowed for this type of query"
),
}
}
}
impl From<VulkanError> for GetResultsError {
fn from(err: VulkanError) -> Self {
match err {
VulkanError::OutOfHostMemory | VulkanError::OutOfDeviceMemory => {
Self::OomError(OomError::from(err))
}
VulkanError::DeviceLost => Self::DeviceLost,
_ => panic!("unexpected error: {:?}", err),
}
}
}
impl From<OomError> for GetResultsError {
fn from(err: OomError) -> Self {
Self::OomError(err)
}
}
impl From<RequirementNotMet> for GetResultsError {
fn from(err: RequirementNotMet) -> Self {
Self::RequirementNotMet {
required_for: err.required_for,
requires_one_of: err.requires_one_of,
}
}
}
/// 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: BufferContents + Sized {
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 [`WITH_AVAILABILITY`] enabled, then an additional element
/// is required per query.
///
/// [`Occlusion`]: QueryType::Occlusion
/// [`Timestamp`]: QueryType::Timestamp
/// [`PipelineStatistics`]: QueryType::PipelineStatistics
/// [`WITH_AVAILABILITY`]: QueryResultFlags::WITH_AVAILABILITY
#[inline]
pub const fn result_len(self) -> DeviceSize {
match self {
Self::Occlusion | Self::Timestamp => 1,
Self::PipelineStatistics(flags) => flags.count() as DeviceSize,
}
}
}
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,
}
}
}
vulkan_bitflags! {
#[non_exhaustive]
/// Flags that control how a query is to be executed.
QueryControlFlags = QueryControlFlags(u32);
/// 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.
PRECISE = PRECISE,
}
vulkan_bitflags! {
#[non_exhaustive]
/// For pipeline statistics queries, the statistics that should be gathered.
QueryPipelineStatisticFlags impl {
/// Returns `true` if `self` contains any flags referring to compute operations.
#[inline]
pub const fn is_compute(self) -> bool {
self.intersects(QueryPipelineStatisticFlags::COMPUTE_SHADER_INVOCATIONS)
}
/// Returns `true` if `self` contains any flags referring to graphics operations.
#[inline]
pub const fn is_graphics(self) -> bool {
self.intersects(
(QueryPipelineStatisticFlags::INPUT_ASSEMBLY_VERTICES)
.union(QueryPipelineStatisticFlags::INPUT_ASSEMBLY_PRIMITIVES)
.union(QueryPipelineStatisticFlags::VERTEX_SHADER_INVOCATIONS)
.union(QueryPipelineStatisticFlags::GEOMETRY_SHADER_INVOCATIONS)
.union(QueryPipelineStatisticFlags::GEOMETRY_SHADER_PRIMITIVES)
.union(QueryPipelineStatisticFlags::CLIPPING_INVOCATIONS)
.union(QueryPipelineStatisticFlags::CLIPPING_PRIMITIVES)
.union(QueryPipelineStatisticFlags::FRAGMENT_SHADER_INVOCATIONS)
.union(QueryPipelineStatisticFlags::TESSELLATION_CONTROL_SHADER_PATCHES)
.union(QueryPipelineStatisticFlags::TESSELLATION_EVALUATION_SHADER_INVOCATIONS),
)
}
}
= QueryPipelineStatisticFlags(u32);
/// Count the number of vertices processed by the input assembly.
INPUT_ASSEMBLY_VERTICES = INPUT_ASSEMBLY_VERTICES,
/// Count the number of primitives processed by the input assembly.
INPUT_ASSEMBLY_PRIMITIVES = INPUT_ASSEMBLY_PRIMITIVES,
/// Count the number of times a vertex shader is invoked.
VERTEX_SHADER_INVOCATIONS = VERTEX_SHADER_INVOCATIONS,
/// Count the number of times a geometry shader is invoked.
GEOMETRY_SHADER_INVOCATIONS = GEOMETRY_SHADER_INVOCATIONS,
/// Count the number of primitives generated by geometry shaders.
GEOMETRY_SHADER_PRIMITIVES = GEOMETRY_SHADER_PRIMITIVES,
/// Count the number of times the clipping stage is invoked on a primitive.
CLIPPING_INVOCATIONS = CLIPPING_INVOCATIONS,
/// Count the number of primitives that are output by the clipping stage.
CLIPPING_PRIMITIVES = CLIPPING_PRIMITIVES,
/// Count the number of times a fragment shader is invoked.
FRAGMENT_SHADER_INVOCATIONS = FRAGMENT_SHADER_INVOCATIONS,
/// Count the number of patches processed by a tessellation control shader.
TESSELLATION_CONTROL_SHADER_PATCHES = TESSELLATION_CONTROL_SHADER_PATCHES,
/// Count the number of times a tessellation evaluation shader is invoked.
TESSELLATION_EVALUATION_SHADER_INVOCATIONS = TESSELLATION_EVALUATION_SHADER_INVOCATIONS,
/// Count the number of times a compute shader is invoked.
COMPUTE_SHADER_INVOCATIONS = COMPUTE_SHADER_INVOCATIONS,
/* TODO: enable
// TODO: document
TASK_SHADER_INVOCATIONS = TASK_SHADER_INVOCATIONS_NV {
device_extensions: [nv_mesh_shader],
},*/
/* TODO: enable
// TODO: document
MESH_SHADER_INVOCATIONS = MESH_SHADER_INVOCATIONS_NV {
device_extensions: [nv_mesh_shader],
},*/
}
vulkan_bitflags! {
#[non_exhaustive]
/// 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.
QueryResultFlags = QueryResultFlags(u32);
/// Wait for the results to become available before writing the results.
WAIT = WAIT,
/// 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.
WITH_AVAILABILITY = WITH_AVAILABILITY,
/// Allow writing partial results to the buffer, instead of waiting until they are fully
/// available.
PARTIAL = PARTIAL,
/* TODO: enable
// TODO: document
WITH_STATUS = WITH_STATUS_KHR {
device_extensions: [khr_video_queue],
},*/
}
#[cfg(test)]
mod tests {
use super::QueryPoolCreateInfo;
use crate::query::{QueryPipelineStatisticFlags, QueryPool, QueryPoolCreationError, QueryType};
#[test]
fn pipeline_statistics_feature() {
let (device, _) = gfx_dev_and_queue!();
let query_type = QueryType::PipelineStatistics(QueryPipelineStatisticFlags::empty());
match QueryPool::new(
device,
QueryPoolCreateInfo {
query_count: 256,
..QueryPoolCreateInfo::query_type(query_type)
},
) {
Err(QueryPoolCreationError::PipelineStatisticsQueryFeatureNotEnabled) => (),
_ => panic!(),
};
}
}