use crate::{
buffer::TypedBufferAccess,
command_buffer::{
allocator::CommandBufferAllocator,
auto::QueryState,
synced::{Command, Resource, SyncCommandBufferBuilder, SyncCommandBufferBuilderError},
sys::UnsafeCommandBufferBuilder,
AutoCommandBufferBuilder,
},
device::DeviceOwned,
query::{
QueriesRange, Query, QueryControlFlags, QueryPool, QueryResultElement, QueryResultFlags,
QueryType,
},
sync::{AccessFlags, PipelineMemoryAccess, PipelineStage, PipelineStages},
DeviceSize, RequirementNotMet, RequiresOneOf, Version, VulkanObject,
};
use std::{
error::Error,
fmt::{Display, Error as FmtError, Formatter},
mem::size_of,
ops::Range,
sync::Arc,
};
impl<L, A> AutoCommandBufferBuilder<L, A>
where
A: CommandBufferAllocator,
{
pub unsafe fn begin_query(
&mut self,
query_pool: Arc<QueryPool>,
query: u32,
flags: QueryControlFlags,
) -> Result<&mut Self, QueryError> {
self.validate_begin_query(&query_pool, query, flags)?;
let ty = query_pool.query_type();
let raw_query_pool = query_pool.handle();
self.inner.begin_query(query_pool, query, flags);
self.query_state.insert(
ty.into(),
QueryState {
query_pool: raw_query_pool,
query,
ty,
flags,
in_subpass: self.render_pass_state.is_some(),
},
);
Ok(self)
}
fn validate_begin_query(
&self,
query_pool: &QueryPool,
query: u32,
flags: QueryControlFlags,
) -> Result<(), QueryError> {
let queue_family_properties = self.queue_family_properties();
if !(queue_family_properties.queue_flags.graphics
|| queue_family_properties.queue_flags.compute)
{
return Err(QueryError::NotSupportedByQueueFamily);
}
let device = self.device();
flags.validate_device(device)?;
assert_eq!(device, query_pool.device());
query_pool.query(query).ok_or(QueryError::OutOfRange)?;
match query_pool.query_type() {
QueryType::Occlusion => {
if !queue_family_properties.queue_flags.graphics {
return Err(QueryError::NotSupportedByQueueFamily);
}
if flags.precise && !device.enabled_features().occlusion_query_precise {
return Err(QueryError::RequirementNotMet {
required_for: "`flags.precise` is set",
requires_one_of: RequiresOneOf {
features: &["occlusion_query_precise"],
..Default::default()
},
});
}
}
QueryType::PipelineStatistics(statistic_flags) => {
if statistic_flags.is_compute() && !queue_family_properties.queue_flags.compute
|| statistic_flags.is_graphics()
&& !queue_family_properties.queue_flags.graphics
{
return Err(QueryError::NotSupportedByQueueFamily);
}
if flags.precise {
return Err(QueryError::InvalidFlags);
}
}
QueryType::Timestamp => return Err(QueryError::NotPermitted),
}
if self
.query_state
.contains_key(&query_pool.query_type().into())
{
return Err(QueryError::QueryIsActive);
}
if let Some(render_pass_state) = &self.render_pass_state {
if query + render_pass_state.view_mask.count_ones() > query_pool.query_count() {
return Err(QueryError::OutOfRangeMultiview);
}
}
Ok(())
}
pub fn end_query(
&mut self,
query_pool: Arc<QueryPool>,
query: u32,
) -> Result<&mut Self, QueryError> {
self.validate_end_query(&query_pool, query)?;
unsafe {
let raw_ty = query_pool.query_type().into();
self.inner.end_query(query_pool, query);
self.query_state.remove(&raw_ty);
}
Ok(self)
}
fn validate_end_query(&self, query_pool: &QueryPool, query: u32) -> Result<(), QueryError> {
let queue_family_properties = self.queue_family_properties();
if !(queue_family_properties.queue_flags.graphics
|| queue_family_properties.queue_flags.compute)
{
return Err(QueryError::NotSupportedByQueueFamily);
}
let device = self.device();
assert_eq!(device, query_pool.device());
if !self
.query_state
.get(&query_pool.query_type().into())
.map_or(false, |state| {
state.query_pool == query_pool.handle() && state.query == query
})
{
return Err(QueryError::QueryNotActive);
}
query_pool.query(query).ok_or(QueryError::OutOfRange)?;
if let Some(render_pass_state) = &self.render_pass_state {
if query + render_pass_state.view_mask.count_ones() > query_pool.query_count() {
return Err(QueryError::OutOfRangeMultiview);
}
}
Ok(())
}
pub unsafe fn write_timestamp(
&mut self,
query_pool: Arc<QueryPool>,
query: u32,
stage: PipelineStage,
) -> Result<&mut Self, QueryError> {
self.validate_write_timestamp(&query_pool, query, stage)?;
self.inner.write_timestamp(query_pool, query, stage);
Ok(self)
}
fn validate_write_timestamp(
&self,
query_pool: &QueryPool,
query: u32,
stage: PipelineStage,
) -> Result<(), QueryError> {
let device = self.device();
if !device.enabled_features().synchronization2 && PipelineStages::from(stage).is_2() {
return Err(QueryError::RequirementNotMet {
required_for: "`stage` has bits set from `VkPipelineStageFlagBits2`",
requires_one_of: RequiresOneOf {
features: &["synchronization2"],
..Default::default()
},
});
}
stage.validate_device(device)?;
let queue_family_properties = self.queue_family_properties();
if !(queue_family_properties.queue_flags.transfer
|| queue_family_properties.queue_flags.graphics
|| queue_family_properties.queue_flags.compute
|| queue_family_properties.queue_flags.video_decode
|| queue_family_properties.queue_flags.video_encode)
{
return Err(QueryError::NotSupportedByQueueFamily);
}
let device = self.device();
assert_eq!(device, query_pool.device());
if !queue_family_properties.supports_stage(stage) {
return Err(QueryError::StageNotSupported);
}
match stage {
PipelineStage::GeometryShader => {
if !device.enabled_features().geometry_shader {
return Err(QueryError::RequirementNotMet {
required_for: "`stage` is `PipelineStage::GeometryShader`",
requires_one_of: RequiresOneOf {
features: &["geometry_shadere"],
..Default::default()
},
});
}
}
PipelineStage::TessellationControlShader
| PipelineStage::TessellationEvaluationShader => {
if !device.enabled_features().tessellation_shader {
return Err(QueryError::RequirementNotMet {
required_for: "`stage` is `PipelineStage::TessellationControlShader` or `PipelineStage::TessellationEvaluationShader`",
requires_one_of: RequiresOneOf {
features: &["tessellation_shader"],
..Default::default()
},
});
}
}
PipelineStage::ConditionalRendering => {
if !device.enabled_features().conditional_rendering {
return Err(QueryError::RequirementNotMet {
required_for: "`stage` is `PipelineStage::ConditionalRendering`",
requires_one_of: RequiresOneOf {
features: &["conditional_rendering"],
..Default::default()
},
});
}
}
PipelineStage::FragmentDensityProcess => {
if !device.enabled_features().fragment_density_map {
return Err(QueryError::RequirementNotMet {
required_for: "`stage` is `PipelineStage::FragmentDensityProcess`",
requires_one_of: RequiresOneOf {
features: &["fragment_density_map"],
..Default::default()
},
});
}
}
PipelineStage::TransformFeedback => {
if !device.enabled_features().transform_feedback {
return Err(QueryError::RequirementNotMet {
required_for: "`stage` is `PipelineStage::TransformFeedback`",
requires_one_of: RequiresOneOf {
features: &["transform_feedback"],
..Default::default()
},
});
}
}
PipelineStage::MeshShader => {
if !device.enabled_features().mesh_shader {
return Err(QueryError::RequirementNotMet {
required_for: "`stage` is `PipelineStage::MeshShader`",
requires_one_of: RequiresOneOf {
features: &["mesh_shader"],
..Default::default()
},
});
}
}
PipelineStage::TaskShader => {
if !device.enabled_features().task_shader {
return Err(QueryError::RequirementNotMet {
required_for: "`stage` is `PipelineStage::TaskShader`",
requires_one_of: RequiresOneOf {
features: &["task_shader"],
..Default::default()
},
});
}
}
PipelineStage::FragmentShadingRateAttachment => {
if !(device.enabled_features().attachment_fragment_shading_rate
|| device.enabled_features().shading_rate_image)
{
return Err(QueryError::RequirementNotMet {
required_for: "`stage` is `PipelineStage::FragmentShadingRateAttachment`",
requires_one_of: RequiresOneOf {
features: &["attachment_fragment_shading_rate", "shading_rate_image"],
..Default::default()
},
});
}
}
PipelineStage::SubpassShading => {
if !device.enabled_features().subpass_shading {
return Err(QueryError::RequirementNotMet {
required_for: "`stage` is `PipelineStage::SubpassShading`",
requires_one_of: RequiresOneOf {
features: &["subpass_shading"],
..Default::default()
},
});
}
}
PipelineStage::InvocationMask => {
if !device.enabled_features().invocation_mask {
return Err(QueryError::RequirementNotMet {
required_for: "`stage` is `PipelineStage::InvocationMask`",
requires_one_of: RequiresOneOf {
features: &["invocation_mask"],
..Default::default()
},
});
}
}
_ => (),
}
if !matches!(query_pool.query_type(), QueryType::Timestamp) {
return Err(QueryError::NotPermitted);
}
if queue_family_properties.timestamp_valid_bits.is_none() {
return Err(QueryError::NoTimestampValidBits);
}
query_pool.query(query).ok_or(QueryError::OutOfRange)?;
if let Some(render_pass_state) = &self.render_pass_state {
if query + render_pass_state.view_mask.count_ones() > query_pool.query_count() {
return Err(QueryError::OutOfRangeMultiview);
}
}
Ok(())
}
pub fn copy_query_pool_results<D, T>(
&mut self,
query_pool: Arc<QueryPool>,
queries: Range<u32>,
destination: Arc<D>,
flags: QueryResultFlags,
) -> Result<&mut Self, QueryError>
where
D: TypedBufferAccess<Content = [T]> + 'static,
T: QueryResultElement,
{
self.validate_copy_query_pool_results(
&query_pool,
queries.clone(),
destination.as_ref(),
flags,
)?;
unsafe {
let per_query_len =
query_pool.query_type().result_len() + flags.with_availability as DeviceSize;
let stride = per_query_len * std::mem::size_of::<T>() as DeviceSize;
self.inner
.copy_query_pool_results(query_pool, queries, destination, stride, flags)?;
}
Ok(self)
}
fn validate_copy_query_pool_results<D, T>(
&self,
query_pool: &QueryPool,
queries: Range<u32>,
destination: &D,
flags: QueryResultFlags,
) -> Result<(), QueryError>
where
D: ?Sized + TypedBufferAccess<Content = [T]>,
T: QueryResultElement,
{
let queue_family_properties = self.queue_family_properties();
if !(queue_family_properties.queue_flags.graphics
|| queue_family_properties.queue_flags.compute)
{
return Err(QueryError::NotSupportedByQueueFamily);
}
if self.render_pass_state.is_some() {
return Err(QueryError::ForbiddenInsideRenderPass);
}
let device = self.device();
let buffer_inner = destination.inner();
assert_eq!(device, buffer_inner.buffer.device());
assert_eq!(device, query_pool.device());
assert!(destination.len() > 0);
debug_assert!(buffer_inner.offset % std::mem::size_of::<T>() as DeviceSize == 0);
query_pool
.queries_range(queries.clone())
.ok_or(QueryError::OutOfRange)?;
let count = queries.end - queries.start;
let per_query_len =
query_pool.query_type().result_len() + flags.with_availability as DeviceSize;
let required_len = per_query_len * count as DeviceSize;
if destination.len() < required_len {
return Err(QueryError::BufferTooSmall {
required_len,
actual_len: destination.len(),
});
}
if !buffer_inner.buffer.usage().transfer_dst {
return Err(QueryError::DestinationMissingUsage);
}
if matches!(query_pool.query_type(), QueryType::Timestamp) && flags.partial {
return Err(QueryError::InvalidFlags);
}
Ok(())
}
pub unsafe fn reset_query_pool(
&mut self,
query_pool: Arc<QueryPool>,
queries: Range<u32>,
) -> Result<&mut Self, QueryError> {
self.validate_reset_query_pool(&query_pool, queries.clone())?;
self.inner.reset_query_pool(query_pool, queries);
Ok(self)
}
fn validate_reset_query_pool(
&self,
query_pool: &QueryPool,
queries: Range<u32>,
) -> Result<(), QueryError> {
if self.render_pass_state.is_some() {
return Err(QueryError::ForbiddenInsideRenderPass);
}
let queue_family_properties = self.queue_family_properties();
if !(queue_family_properties.queue_flags.graphics
|| queue_family_properties.queue_flags.compute)
{
return Err(QueryError::NotSupportedByQueueFamily);
}
let device = self.device();
assert_eq!(device, query_pool.device());
query_pool
.queries_range(queries.clone())
.ok_or(QueryError::OutOfRange)?;
if self
.query_state
.values()
.any(|state| state.query_pool == query_pool.handle() && queries.contains(&state.query))
{
return Err(QueryError::QueryIsActive);
}
Ok(())
}
}
impl SyncCommandBufferBuilder {
#[inline]
pub unsafe fn begin_query(
&mut self,
query_pool: Arc<QueryPool>,
query: u32,
flags: QueryControlFlags,
) {
struct Cmd {
query_pool: Arc<QueryPool>,
query: u32,
flags: QueryControlFlags,
}
impl Command for Cmd {
fn name(&self) -> &'static str {
"begin_query"
}
unsafe fn send(&self, out: &mut UnsafeCommandBufferBuilder) {
out.begin_query(self.query_pool.query(self.query).unwrap(), self.flags);
}
}
self.commands.push(Box::new(Cmd {
query_pool,
query,
flags,
}));
}
#[inline]
pub unsafe fn end_query(&mut self, query_pool: Arc<QueryPool>, query: u32) {
struct Cmd {
query_pool: Arc<QueryPool>,
query: u32,
}
impl Command for Cmd {
fn name(&self) -> &'static str {
"end_query"
}
unsafe fn send(&self, out: &mut UnsafeCommandBufferBuilder) {
out.end_query(self.query_pool.query(self.query).unwrap());
}
}
self.commands.push(Box::new(Cmd { query_pool, query }));
}
#[inline]
pub unsafe fn write_timestamp(
&mut self,
query_pool: Arc<QueryPool>,
query: u32,
stage: PipelineStage,
) {
struct Cmd {
query_pool: Arc<QueryPool>,
query: u32,
stage: PipelineStage,
}
impl Command for Cmd {
fn name(&self) -> &'static str {
"write_timestamp"
}
unsafe fn send(&self, out: &mut UnsafeCommandBufferBuilder) {
out.write_timestamp(self.query_pool.query(self.query).unwrap(), self.stage);
}
}
self.commands.push(Box::new(Cmd {
query_pool,
query,
stage,
}));
}
pub unsafe fn copy_query_pool_results<D, T>(
&mut self,
query_pool: Arc<QueryPool>,
queries: Range<u32>,
destination: Arc<D>,
stride: DeviceSize,
flags: QueryResultFlags,
) -> Result<(), SyncCommandBufferBuilderError>
where
D: TypedBufferAccess<Content = [T]> + 'static,
T: QueryResultElement,
{
struct Cmd<D> {
query_pool: Arc<QueryPool>,
queries: Range<u32>,
destination: Arc<D>,
stride: DeviceSize,
flags: QueryResultFlags,
}
impl<D, T> Command for Cmd<D>
where
D: TypedBufferAccess<Content = [T]> + 'static,
T: QueryResultElement,
{
fn name(&self) -> &'static str {
"copy_query_pool_results"
}
unsafe fn send(&self, out: &mut UnsafeCommandBufferBuilder) {
out.copy_query_pool_results(
self.query_pool.queries_range(self.queries.clone()).unwrap(),
self.destination.as_ref(),
self.stride,
self.flags,
);
}
}
let resources = [(
"destination".into(),
Resource::Buffer {
buffer: destination.clone(),
range: 0..destination.size(), memory: PipelineMemoryAccess {
stages: PipelineStages {
all_transfer: true,
..PipelineStages::empty()
},
access: AccessFlags {
transfer_write: true,
..AccessFlags::empty()
},
exclusive: true,
},
},
)];
for resource in &resources {
self.check_resource_conflicts(resource)?;
}
self.commands.push(Box::new(Cmd {
query_pool,
queries,
destination,
stride,
flags,
}));
for resource in resources {
self.add_resource(resource);
}
Ok(())
}
#[inline]
pub unsafe fn reset_query_pool(&mut self, query_pool: Arc<QueryPool>, queries: Range<u32>) {
struct Cmd {
query_pool: Arc<QueryPool>,
queries: Range<u32>,
}
impl Command for Cmd {
fn name(&self) -> &'static str {
"reset_query_pool"
}
unsafe fn send(&self, out: &mut UnsafeCommandBufferBuilder) {
out.reset_query_pool(self.query_pool.queries_range(self.queries.clone()).unwrap());
}
}
self.commands.push(Box::new(Cmd {
query_pool,
queries,
}));
}
}
impl UnsafeCommandBufferBuilder {
#[inline]
pub unsafe fn begin_query(&mut self, query: Query<'_>, flags: QueryControlFlags) {
let fns = self.device.fns();
let flags = if flags.precise {
ash::vk::QueryControlFlags::PRECISE
} else {
ash::vk::QueryControlFlags::empty()
};
(fns.v1_0.cmd_begin_query)(self.handle, query.pool().handle(), query.index(), flags);
}
#[inline]
pub unsafe fn end_query(&mut self, query: Query<'_>) {
let fns = self.device.fns();
(fns.v1_0.cmd_end_query)(self.handle, query.pool().handle(), query.index());
}
#[inline]
pub unsafe fn write_timestamp(&mut self, query: Query<'_>, stage: PipelineStage) {
let fns = self.device.fns();
if self.device.enabled_features().synchronization2 {
if self.device.api_version() >= Version::V1_3 {
(fns.v1_3.cmd_write_timestamp2)(
self.handle,
stage.into(),
query.pool().handle(),
query.index(),
);
} else {
debug_assert!(self.device.enabled_extensions().khr_synchronization2);
(fns.khr_synchronization2.cmd_write_timestamp2_khr)(
self.handle,
stage.into(),
query.pool().handle(),
query.index(),
);
}
} else {
(fns.v1_0.cmd_write_timestamp)(
self.handle,
stage.into(),
query.pool().handle(),
query.index(),
);
}
}
pub unsafe fn copy_query_pool_results<D, T>(
&mut self,
queries: QueriesRange<'_>,
destination: &D,
stride: DeviceSize,
flags: QueryResultFlags,
) where
D: TypedBufferAccess<Content = [T]>,
T: QueryResultElement,
{
let destination = destination.inner();
let range = queries.range();
debug_assert!(destination.offset < destination.buffer.size());
debug_assert!(destination.buffer.usage().transfer_dst);
debug_assert!(destination.offset % size_of::<T>() as DeviceSize == 0);
debug_assert!(stride % size_of::<T>() as DeviceSize == 0);
let fns = self.device.fns();
(fns.v1_0.cmd_copy_query_pool_results)(
self.handle,
queries.pool().handle(),
range.start,
range.end - range.start,
destination.buffer.handle(),
destination.offset,
stride,
ash::vk::QueryResultFlags::from(flags) | T::FLAG,
);
}
#[inline]
pub unsafe fn reset_query_pool(&mut self, queries: QueriesRange<'_>) {
let range = queries.range();
let fns = self.device.fns();
(fns.v1_0.cmd_reset_query_pool)(
self.handle,
queries.pool().handle(),
range.start,
range.end - range.start,
);
}
}
#[derive(Clone, Debug)]
pub enum QueryError {
SyncCommandBufferBuilderError(SyncCommandBufferBuilderError),
RequirementNotMet {
required_for: &'static str,
requires_one_of: RequiresOneOf,
},
BufferTooSmall {
required_len: DeviceSize,
actual_len: DeviceSize,
},
DestinationMissingUsage,
ForbiddenInsideRenderPass,
InvalidFlags,
NoTimestampValidBits,
NotPermitted,
NotSupportedByQueueFamily,
OutOfRange,
OutOfRangeMultiview,
QueryIsActive,
QueryNotActive,
StageNotSupported,
}
impl Error for QueryError {}
impl Display for QueryError {
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), FmtError> {
match self {
Self::SyncCommandBufferBuilderError(_) => write!(f, "a SyncCommandBufferBuilderError"),
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 copy operation")
}
Self::DestinationMissingUsage => write!(
f,
"the destination buffer is missing the `transfer_dst` usage",
),
Self::ForbiddenInsideRenderPass => {
write!(f, "operation forbidden inside of a render pass")
}
Self::InvalidFlags => write!(
f,
"the provided flags are not allowed for this type of query",
),
Self::NoTimestampValidBits => {
write!(f, "the queue family's timestamp_valid_bits value is None")
}
Self::NotPermitted => write!(f, "this operation is not permitted on this query type"),
Self::NotSupportedByQueueFamily => {
write!(f, "the queue family doesn't allow this operation")
}
Self::OutOfRange => write!(f, "the provided query index is not valid for this pool"),
Self::OutOfRangeMultiview => write!(
f,
"the provided query index plus the number of views in the current render subpass \
is greater than the number of queries in the pool",
),
Self::QueryIsActive => write!(
f,
"a query is active that conflicts with the current operation"
),
Self::QueryNotActive => write!(f, "this query was not active"),
Self::StageNotSupported => {
write!(f, "the provided stage is not supported by the queue family")
}
}
}
}
impl From<SyncCommandBufferBuilderError> for QueryError {
fn from(err: SyncCommandBufferBuilderError) -> Self {
Self::SyncCommandBufferBuilderError(err)
}
}
impl From<RequirementNotMet> for QueryError {
fn from(err: RequirementNotMet) -> Self {
Self::RequirementNotMet {
required_for: err.required_for,
requires_one_of: err.requires_one_of,
}
}
}