use crate::{
buffer::BufferContents,
device::{Device, DeviceOwned},
instance::InstanceOwnedDebugWrapper,
macros::{impl_id_counter, vulkan_bitflags},
DeviceSize, Requires, RequiresAllOf, RequiresOneOf, Validated, ValidationError, VulkanError,
VulkanObject,
};
use std::{
ffi::c_void,
mem::{size_of_val, MaybeUninit},
num::NonZeroU64,
ops::Range,
ptr,
sync::Arc,
};
#[derive(Debug)]
pub struct QueryPool {
handle: ash::vk::QueryPool,
device: InstanceOwnedDebugWrapper<Arc<Device>>,
id: NonZeroU64,
query_type: QueryType,
query_count: u32,
}
impl QueryPool {
#[inline]
pub fn new(
device: Arc<Device>,
create_info: QueryPoolCreateInfo,
) -> Result<Arc<QueryPool>, Validated<VulkanError>> {
Self::validate_new(&device, &create_info)?;
unsafe { Ok(Self::new_unchecked(device, create_info)?) }
}
#[cfg_attr(not(feature = "document_unchecked"), doc(hidden))]
pub unsafe fn new_unchecked(
device: Arc<Device>,
create_info: QueryPoolCreateInfo,
) -> Result<Arc<QueryPool>, VulkanError> {
let &QueryPoolCreateInfo {
ref query_type,
query_count,
_ne: _,
} = &create_info;
let pipeline_statistics = if let &QueryType::PipelineStatistics(flags) = query_type {
flags.into()
} else {
ash::vk::QueryPipelineStatisticFlags::empty()
};
let create_info_vk = 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_vk,
ptr::null(),
output.as_mut_ptr(),
)
.result()
.map_err(VulkanError::from)?;
output.assume_init()
};
Ok(Self::from_handle(device, handle, create_info))
}
fn validate_new(
device: &Device,
create_info: &QueryPoolCreateInfo,
) -> Result<(), Box<ValidationError>> {
create_info
.validate(device)
.map_err(|err| err.add_context("create_info"))?;
Ok(())
}
#[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: InstanceOwnedDebugWrapper(device),
id: Self::next_id(),
query_type,
query_count,
})
}
#[inline]
pub fn query_type(&self) -> &QueryType {
&self.query_type
}
#[inline]
pub fn query_count(&self) -> u32 {
self.query_count
}
#[inline]
pub fn get_results<T>(
&self,
range: Range<u32>,
destination: &mut [T],
flags: QueryResultFlags,
) -> Result<bool, Validated<VulkanError>>
where
T: QueryResultElement,
{
self.validate_get_results(range.clone(), destination, flags)?;
unsafe { Ok(self.get_results_unchecked(range, destination, flags)?) }
}
fn validate_get_results<T>(
&self,
range: Range<u32>,
destination: &[T],
flags: QueryResultFlags,
) -> Result<(), Box<ValidationError>>
where
T: QueryResultElement,
{
flags.validate_device(&self.device).map_err(|err| {
err.add_context("flags")
.set_vuids(&["VUID-vkGetQueryPoolResults-flags-parameter"])
})?;
if destination.is_empty() {
return Err(Box::new(ValidationError {
context: "destination".into(),
problem: "is empty".into(),
vuids: &["VUID-vkGetQueryPoolResults-dataSize-arraylength"],
..Default::default()
}));
}
if range.is_empty() {
return Err(Box::new(ValidationError {
context: "range".into(),
problem: "is empty".into(),
..Default::default()
}));
}
if range.end > self.query_count {
return Err(Box::new(ValidationError {
problem: "`range.end` is greater than `self.query_count`".into(),
vuids: &[
"VUID-vkGetQueryPoolResults-firstQuery-00813",
"VUID-vkGetQueryPoolResults-firstQuery-00816",
],
..Default::default()
}));
}
let per_query_len = self.query_type.result_len()
+ flags.intersects(QueryResultFlags::WITH_AVAILABILITY) as DeviceSize;
let required_len = per_query_len * range.len() as DeviceSize;
if (destination.len() as DeviceSize) < required_len {
return Err(Box::new(ValidationError {
context: "destination.len()".into(),
problem: "is less than the number of elements required for the query type, and \
the provided range and flags"
.into(),
vuids: &["VUID-vkGetQueryPoolResults-dataSize-00817"],
..Default::default()
}));
}
match &self.query_type {
QueryType::Timestamp => {
if flags.intersects(QueryResultFlags::PARTIAL) {
return Err(Box::new(ValidationError {
problem: "`self.query_type()` is `QueryType::Timestamp`, but \
`flags` contains `QueryResultFlags::PARTIAL`"
.into(),
vuids: &["VUID-vkGetQueryPoolResults-queryType-00818"],
..Default::default()
}));
}
}
QueryType::Occlusion
| QueryType::PipelineStatistics(_)
| QueryType::AccelerationStructureCompactedSize
| QueryType::AccelerationStructureSerializationSize
| QueryType::AccelerationStructureSerializationBottomLevelPointers
| QueryType::AccelerationStructureSize => (),
}
Ok(())
}
#[cfg_attr(not(feature = "document_unchecked"), doc(hidden))]
pub unsafe fn get_results_unchecked<T>(
&self,
range: Range<u32>,
destination: &mut [T],
flags: QueryResultFlags,
) -> Result<bool, VulkanError>
where
T: QueryResultElement,
{
let per_query_len = self.query_type.result_len()
+ flags.intersects(QueryResultFlags::WITH_AVAILABILITY) as DeviceSize;
let stride = per_query_len * std::mem::size_of::<T>() as DeviceSize;
let result = unsafe {
let fns = self.device.fns();
(fns.v1_0.get_query_pool_results)(
self.device.handle(),
self.handle(),
range.start,
range.len() as u32,
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)),
}
}
}
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);
#[derive(Clone, Debug)]
pub struct QueryPoolCreateInfo {
pub query_type: QueryType,
pub query_count: u32,
pub _ne: crate::NonExhaustive,
}
impl QueryPoolCreateInfo {
#[inline]
pub fn query_type(query_type: QueryType) -> Self {
Self {
query_type,
query_count: 0,
_ne: crate::NonExhaustive(()),
}
}
pub(crate) fn validate(&self, device: &Device) -> Result<(), Box<ValidationError>> {
let &Self {
ref query_type,
query_count,
_ne: _,
} = self;
query_type.validate_device(device).map_err(|err| {
err.add_context("query_type")
.set_vuids(&["VUID-VkQueryPoolCreateInfo-queryType-parameter"])
})?;
match query_type {
QueryType::PipelineStatistics(flags) => {
if !device.enabled_features().pipeline_statistics_query {
return Err(Box::new(ValidationError {
context: "query_type".into(),
problem: "is `QueryType::PipelineStatistics`".into(),
requires_one_of: RequiresOneOf(&[RequiresAllOf(&[Requires::Feature(
"pipeline_statistics_query",
)])]),
vuids: &["VUID-VkQueryPoolCreateInfo-queryType-00791"],
}));
}
flags.validate_device(device).map_err(|err| {
err.add_context("query_type.flags")
.set_vuids(&["VUID-VkQueryPoolCreateInfo-queryType-00792"])
})?;
}
QueryType::Occlusion
| QueryType::Timestamp
| QueryType::AccelerationStructureCompactedSize
| QueryType::AccelerationStructureSerializationSize
| QueryType::AccelerationStructureSerializationBottomLevelPointers
| QueryType::AccelerationStructureSize => (),
};
if query_count == 0 {
return Err(Box::new(ValidationError {
context: "query_count".into(),
problem: "is 0".into(),
vuids: &["VUID-VkQueryPoolCreateInfo-queryCount-02763"],
..Default::default()
}));
}
Ok(())
}
}
#[derive(Clone, Debug)]
#[repr(i32)]
#[non_exhaustive]
pub enum QueryType {
Occlusion = ash::vk::QueryType::OCCLUSION.as_raw(),
PipelineStatistics(QueryPipelineStatisticFlags) =
ash::vk::QueryType::PIPELINE_STATISTICS.as_raw(),
Timestamp = ash::vk::QueryType::TIMESTAMP.as_raw(),
AccelerationStructureCompactedSize =
ash::vk::QueryType::ACCELERATION_STRUCTURE_COMPACTED_SIZE_KHR.as_raw(),
AccelerationStructureSerializationSize =
ash::vk::QueryType::ACCELERATION_STRUCTURE_SERIALIZATION_SIZE_KHR.as_raw(),
AccelerationStructureSerializationBottomLevelPointers =
ash::vk::QueryType::ACCELERATION_STRUCTURE_SERIALIZATION_BOTTOM_LEVEL_POINTERS_KHR.as_raw(),
AccelerationStructureSize = ash::vk::QueryType::ACCELERATION_STRUCTURE_SIZE_KHR.as_raw(),
}
impl QueryType {
#[inline]
pub const fn result_len(&self) -> DeviceSize {
match self {
Self::Occlusion
| Self::Timestamp
| Self::AccelerationStructureCompactedSize
| Self::AccelerationStructureSerializationSize
| Self::AccelerationStructureSerializationBottomLevelPointers
| Self::AccelerationStructureSize => 1,
Self::PipelineStatistics(flags) => flags.count() as DeviceSize,
}
}
pub(crate) fn validate_device(&self, device: &Device) -> Result<(), Box<ValidationError>> {
match self {
QueryType::Occlusion => (),
QueryType::PipelineStatistics(_) => (),
QueryType::Timestamp => (),
QueryType::AccelerationStructureCompactedSize => {
if !device.enabled_extensions().khr_acceleration_structure {
return Err(Box::new(ValidationError {
problem: "is `QueryType::AccelerationStructureCompactedSize`".into(),
requires_one_of: RequiresOneOf(&[RequiresAllOf(&[
Requires::DeviceExtension("khr_acceleration_structure"),
])]),
..Default::default()
}));
}
}
QueryType::AccelerationStructureSerializationSize => {
if !device.enabled_extensions().khr_acceleration_structure {
return Err(Box::new(ValidationError {
problem: "is `QueryType::AccelerationStructureSerializationSize`".into(),
requires_one_of: RequiresOneOf(&[RequiresAllOf(&[
Requires::DeviceExtension("khr_acceleration_structure"),
])]),
..Default::default()
}));
}
}
QueryType::AccelerationStructureSerializationBottomLevelPointers => {
if !device.enabled_extensions().khr_ray_tracing_maintenance1 {
return Err(Box::new(ValidationError {
problem:
"is `QueryType::AccelerationStructureSerializationBottomLevelPointers`"
.into(),
requires_one_of: RequiresOneOf(&[RequiresAllOf(&[
Requires::DeviceExtension("khr_ray_tracing_maintenance1"),
])]),
..Default::default()
}));
}
}
QueryType::AccelerationStructureSize => {
if !device.enabled_extensions().khr_ray_tracing_maintenance1 {
return Err(Box::new(ValidationError {
problem: "is `QueryType::AccelerationStructureSize`".into(),
requires_one_of: RequiresOneOf(&[RequiresAllOf(&[
Requires::DeviceExtension("khr_ray_tracing_maintenance1"),
])]),
..Default::default()
}));
}
}
}
Ok(())
}
}
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,
QueryType::AccelerationStructureCompactedSize => {
ash::vk::QueryType::ACCELERATION_STRUCTURE_COMPACTED_SIZE_KHR
}
QueryType::AccelerationStructureSerializationSize => {
ash::vk::QueryType::ACCELERATION_STRUCTURE_SERIALIZATION_SIZE_KHR
}
QueryType::AccelerationStructureSerializationBottomLevelPointers => {
ash::vk::QueryType::ACCELERATION_STRUCTURE_SERIALIZATION_BOTTOM_LEVEL_POINTERS_KHR
}
QueryType::AccelerationStructureSize => {
ash::vk::QueryType::ACCELERATION_STRUCTURE_SIZE_KHR
}
}
}
}
vulkan_bitflags! {
#[non_exhaustive]
QueryControlFlags = QueryControlFlags(u32);
PRECISE = PRECISE,
}
vulkan_bitflags! {
#[non_exhaustive]
QueryPipelineStatisticFlags impl {
#[inline]
pub const fn is_compute(self) -> bool {
self.intersects(QueryPipelineStatisticFlags::COMPUTE_SHADER_INVOCATIONS)
}
#[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);
INPUT_ASSEMBLY_VERTICES = INPUT_ASSEMBLY_VERTICES,
INPUT_ASSEMBLY_PRIMITIVES = INPUT_ASSEMBLY_PRIMITIVES,
VERTEX_SHADER_INVOCATIONS = VERTEX_SHADER_INVOCATIONS,
GEOMETRY_SHADER_INVOCATIONS = GEOMETRY_SHADER_INVOCATIONS,
GEOMETRY_SHADER_PRIMITIVES = GEOMETRY_SHADER_PRIMITIVES,
CLIPPING_INVOCATIONS = CLIPPING_INVOCATIONS,
CLIPPING_PRIMITIVES = CLIPPING_PRIMITIVES,
FRAGMENT_SHADER_INVOCATIONS = FRAGMENT_SHADER_INVOCATIONS,
TESSELLATION_CONTROL_SHADER_PATCHES = TESSELLATION_CONTROL_SHADER_PATCHES,
TESSELLATION_EVALUATION_SHADER_INVOCATIONS = TESSELLATION_EVALUATION_SHADER_INVOCATIONS,
COMPUTE_SHADER_INVOCATIONS = COMPUTE_SHADER_INVOCATIONS,
}
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;
}
vulkan_bitflags! {
#[non_exhaustive]
QueryResultFlags = QueryResultFlags(u32);
WAIT = WAIT,
WITH_AVAILABILITY = WITH_AVAILABILITY,
PARTIAL = PARTIAL,
}
#[cfg(test)]
mod tests {
use super::QueryPoolCreateInfo;
use crate::{
query::{QueryPipelineStatisticFlags, QueryPool, QueryType},
Validated,
};
#[test]
fn pipeline_statistics_feature() {
let (device, _) = gfx_dev_and_queue!();
let query_type = QueryType::PipelineStatistics(QueryPipelineStatisticFlags::empty());
assert!(matches!(
QueryPool::new(
device,
QueryPoolCreateInfo {
query_count: 256,
..QueryPoolCreateInfo::query_type(query_type)
},
),
Err(Validated::ValidationError(_)),
));
}
}