wgpu_core/command/
query.rs

1use hal::CommandEncoder as _;
2
3#[cfg(feature = "trace")]
4use crate::device::trace::Command as TraceCommand;
5use crate::{
6    command::{CommandBuffer, CommandEncoderError},
7    device::{DeviceError, MissingFeatures},
8    global::Global,
9    hal_api::HalApi,
10    id::{self, Id},
11    init_tracker::MemoryInitKind,
12    resource::{QuerySet, Resource},
13    storage::Storage,
14    Epoch, FastHashMap, Index,
15};
16use std::{iter, marker::PhantomData};
17use thiserror::Error;
18use wgt::BufferAddress;
19
20#[derive(Debug)]
21pub(crate) struct QueryResetMap<A: HalApi> {
22    map: FastHashMap<Index, (Vec<bool>, Epoch)>,
23    _phantom: PhantomData<A>,
24}
25impl<A: HalApi> QueryResetMap<A> {
26    pub fn new() -> Self {
27        Self {
28            map: FastHashMap::default(),
29            _phantom: PhantomData,
30        }
31    }
32
33    pub fn use_query_set(
34        &mut self,
35        id: id::QuerySetId,
36        query_set: &QuerySet<A>,
37        query: u32,
38    ) -> bool {
39        let (index, epoch, _) = id.unzip();
40        let vec_pair = self
41            .map
42            .entry(index)
43            .or_insert_with(|| (vec![false; query_set.desc.count as usize], epoch));
44
45        std::mem::replace(&mut vec_pair.0[query as usize], true)
46    }
47
48    pub fn reset_queries(
49        &mut self,
50        raw_encoder: &mut A::CommandEncoder,
51        query_set_storage: &Storage<QuerySet<A>>,
52        backend: wgt::Backend,
53    ) -> Result<(), id::QuerySetId> {
54        for (query_set_id, (state, epoch)) in self.map.drain() {
55            let id = Id::zip(query_set_id, epoch, backend);
56            let query_set = query_set_storage.get(id).map_err(|_| id)?;
57
58            debug_assert_eq!(state.len(), query_set.desc.count as usize);
59
60            // Need to find all "runs" of values which need resets. If the state vector is:
61            // [false, true, true, false, true], we want to reset [1..3, 4..5]. This minimizes
62            // the amount of resets needed.
63            let mut run_start: Option<u32> = None;
64            for (idx, value) in state.into_iter().chain(iter::once(false)).enumerate() {
65                match (run_start, value) {
66                    // We're inside of a run, do nothing
67                    (Some(..), true) => {}
68                    // We've hit the end of a run, dispatch a reset
69                    (Some(start), false) => {
70                        run_start = None;
71                        unsafe { raw_encoder.reset_queries(query_set.raw(), start..idx as u32) };
72                    }
73                    // We're starting a run
74                    (None, true) => {
75                        run_start = Some(idx as u32);
76                    }
77                    // We're in a run of falses, do nothing.
78                    (None, false) => {}
79                }
80            }
81        }
82
83        Ok(())
84    }
85}
86
87#[derive(Debug, Copy, Clone, PartialEq, Eq)]
88pub enum SimplifiedQueryType {
89    Occlusion,
90    Timestamp,
91    PipelineStatistics,
92}
93impl From<wgt::QueryType> for SimplifiedQueryType {
94    fn from(q: wgt::QueryType) -> Self {
95        match q {
96            wgt::QueryType::Occlusion => SimplifiedQueryType::Occlusion,
97            wgt::QueryType::Timestamp => SimplifiedQueryType::Timestamp,
98            wgt::QueryType::PipelineStatistics(..) => SimplifiedQueryType::PipelineStatistics,
99        }
100    }
101}
102
103/// Error encountered when dealing with queries
104#[derive(Clone, Debug, Error)]
105#[non_exhaustive]
106pub enum QueryError {
107    #[error(transparent)]
108    Device(#[from] DeviceError),
109    #[error(transparent)]
110    Encoder(#[from] CommandEncoderError),
111    #[error(transparent)]
112    MissingFeature(#[from] MissingFeatures),
113    #[error("Error encountered while trying to use queries")]
114    Use(#[from] QueryUseError),
115    #[error("Error encountered while trying to resolve a query")]
116    Resolve(#[from] ResolveError),
117    #[error("Buffer {0:?} is invalid or destroyed")]
118    InvalidBuffer(id::BufferId),
119    #[error("QuerySet {0:?} is invalid or destroyed")]
120    InvalidQuerySet(id::QuerySetId),
121}
122
123impl crate::error::PrettyError for QueryError {
124    fn fmt_pretty(&self, fmt: &mut crate::error::ErrorFormatter) {
125        fmt.error(self);
126        match *self {
127            Self::InvalidBuffer(id) => fmt.buffer_label(&id),
128            Self::InvalidQuerySet(id) => fmt.query_set_label(&id),
129
130            _ => {}
131        }
132    }
133}
134
135/// Error encountered while trying to use queries
136#[derive(Clone, Debug, Error)]
137#[non_exhaustive]
138pub enum QueryUseError {
139    #[error("Query {query_index} is out of bounds for a query set of size {query_set_size}")]
140    OutOfBounds {
141        query_index: u32,
142        query_set_size: u32,
143    },
144    #[error("Query {query_index} has already been used within the same renderpass. Queries must only be used once per renderpass")]
145    UsedTwiceInsideRenderpass { query_index: u32 },
146    #[error("Query {new_query_index} was started while query {active_query_index} was already active. No more than one statistic or occlusion query may be active at once")]
147    AlreadyStarted {
148        active_query_index: u32,
149        new_query_index: u32,
150    },
151    #[error("Query was stopped while there was no active query")]
152    AlreadyStopped,
153    #[error("A query of type {query_type:?} was started using a query set of type {set_type:?}")]
154    IncompatibleType {
155        set_type: SimplifiedQueryType,
156        query_type: SimplifiedQueryType,
157    },
158}
159
160/// Error encountered while trying to resolve a query.
161#[derive(Clone, Debug, Error)]
162#[non_exhaustive]
163pub enum ResolveError {
164    #[error("Queries can only be resolved to buffers that contain the QUERY_RESOLVE usage")]
165    MissingBufferUsage,
166    #[error("Resolve buffer offset has to be aligned to `QUERY_RESOLVE_BUFFER_ALIGNMENT")]
167    BufferOffsetAlignment,
168    #[error("Resolving queries {start_query}..{end_query} would overrun the query set of size {query_set_size}")]
169    QueryOverrun {
170        start_query: u32,
171        end_query: u32,
172        query_set_size: u32,
173    },
174    #[error("Resolving queries {start_query}..{end_query} ({stride} byte queries) will end up overrunning the bounds of the destination buffer of size {buffer_size} using offsets {buffer_start_offset}..{buffer_end_offset}")]
175    BufferOverrun {
176        start_query: u32,
177        end_query: u32,
178        stride: u32,
179        buffer_size: BufferAddress,
180        buffer_start_offset: BufferAddress,
181        buffer_end_offset: BufferAddress,
182    },
183}
184
185impl<A: HalApi> QuerySet<A> {
186    fn validate_query(
187        &self,
188        query_set_id: id::QuerySetId,
189        query_type: SimplifiedQueryType,
190        query_index: u32,
191        reset_state: Option<&mut QueryResetMap<A>>,
192    ) -> Result<&A::QuerySet, QueryUseError> {
193        // We need to defer our resets because we are in a renderpass,
194        // add the usage to the reset map.
195        if let Some(reset) = reset_state {
196            let used = reset.use_query_set(query_set_id, self, query_index);
197            if used {
198                return Err(QueryUseError::UsedTwiceInsideRenderpass { query_index });
199            }
200        }
201
202        let simple_set_type = SimplifiedQueryType::from(self.desc.ty);
203        if simple_set_type != query_type {
204            return Err(QueryUseError::IncompatibleType {
205                query_type,
206                set_type: simple_set_type,
207            });
208        }
209
210        if query_index >= self.desc.count {
211            return Err(QueryUseError::OutOfBounds {
212                query_index,
213                query_set_size: self.desc.count,
214            });
215        }
216
217        Ok(self.raw())
218    }
219
220    pub(super) fn validate_and_write_timestamp(
221        &self,
222        raw_encoder: &mut A::CommandEncoder,
223        query_set_id: id::QuerySetId,
224        query_index: u32,
225        reset_state: Option<&mut QueryResetMap<A>>,
226    ) -> Result<(), QueryUseError> {
227        let needs_reset = reset_state.is_none();
228        let query_set = self.validate_query(
229            query_set_id,
230            SimplifiedQueryType::Timestamp,
231            query_index,
232            reset_state,
233        )?;
234
235        unsafe {
236            // If we don't have a reset state tracker which can defer resets, we must reset now.
237            if needs_reset {
238                raw_encoder.reset_queries(self.raw(), query_index..(query_index + 1));
239            }
240            raw_encoder.write_timestamp(query_set, query_index);
241        }
242
243        Ok(())
244    }
245
246    pub(super) fn validate_and_begin_occlusion_query(
247        &self,
248        raw_encoder: &mut A::CommandEncoder,
249        query_set_id: id::QuerySetId,
250        query_index: u32,
251        reset_state: Option<&mut QueryResetMap<A>>,
252        active_query: &mut Option<(id::QuerySetId, u32)>,
253    ) -> Result<(), QueryUseError> {
254        let needs_reset = reset_state.is_none();
255        let query_set = self.validate_query(
256            query_set_id,
257            SimplifiedQueryType::Occlusion,
258            query_index,
259            reset_state,
260        )?;
261
262        if let Some((_old_id, old_idx)) = active_query.replace((query_set_id, query_index)) {
263            return Err(QueryUseError::AlreadyStarted {
264                active_query_index: old_idx,
265                new_query_index: query_index,
266            });
267        }
268
269        unsafe {
270            // If we don't have a reset state tracker which can defer resets, we must reset now.
271            if needs_reset {
272                raw_encoder
273                    .reset_queries(self.raw.as_ref().unwrap(), query_index..(query_index + 1));
274            }
275            raw_encoder.begin_query(query_set, query_index);
276        }
277
278        Ok(())
279    }
280
281    pub(super) fn validate_and_begin_pipeline_statistics_query(
282        &self,
283        raw_encoder: &mut A::CommandEncoder,
284        query_set_id: id::QuerySetId,
285        query_index: u32,
286        reset_state: Option<&mut QueryResetMap<A>>,
287        active_query: &mut Option<(id::QuerySetId, u32)>,
288    ) -> Result<(), QueryUseError> {
289        let needs_reset = reset_state.is_none();
290        let query_set = self.validate_query(
291            query_set_id,
292            SimplifiedQueryType::PipelineStatistics,
293            query_index,
294            reset_state,
295        )?;
296
297        if let Some((_old_id, old_idx)) = active_query.replace((query_set_id, query_index)) {
298            return Err(QueryUseError::AlreadyStarted {
299                active_query_index: old_idx,
300                new_query_index: query_index,
301            });
302        }
303
304        unsafe {
305            // If we don't have a reset state tracker which can defer resets, we must reset now.
306            if needs_reset {
307                raw_encoder.reset_queries(self.raw(), query_index..(query_index + 1));
308            }
309            raw_encoder.begin_query(query_set, query_index);
310        }
311
312        Ok(())
313    }
314}
315
316pub(super) fn end_occlusion_query<A: HalApi>(
317    raw_encoder: &mut A::CommandEncoder,
318    storage: &Storage<QuerySet<A>>,
319    active_query: &mut Option<(id::QuerySetId, u32)>,
320) -> Result<(), QueryUseError> {
321    if let Some((query_set_id, query_index)) = active_query.take() {
322        // We can unwrap here as the validity was validated when the active query was set
323        let query_set = storage.get(query_set_id).unwrap();
324
325        unsafe { raw_encoder.end_query(query_set.raw.as_ref().unwrap(), query_index) };
326
327        Ok(())
328    } else {
329        Err(QueryUseError::AlreadyStopped)
330    }
331}
332
333pub(super) fn end_pipeline_statistics_query<A: HalApi>(
334    raw_encoder: &mut A::CommandEncoder,
335    storage: &Storage<QuerySet<A>>,
336    active_query: &mut Option<(id::QuerySetId, u32)>,
337) -> Result<(), QueryUseError> {
338    if let Some((query_set_id, query_index)) = active_query.take() {
339        // We can unwrap here as the validity was validated when the active query was set
340        let query_set = storage.get(query_set_id).unwrap();
341
342        unsafe { raw_encoder.end_query(query_set.raw(), query_index) };
343
344        Ok(())
345    } else {
346        Err(QueryUseError::AlreadyStopped)
347    }
348}
349
350impl Global {
351    pub fn command_encoder_write_timestamp<A: HalApi>(
352        &self,
353        command_encoder_id: id::CommandEncoderId,
354        query_set_id: id::QuerySetId,
355        query_index: u32,
356    ) -> Result<(), QueryError> {
357        let hub = A::hub(self);
358
359        let cmd_buf = CommandBuffer::get_encoder(hub, command_encoder_id)?;
360
361        cmd_buf
362            .device
363            .require_features(wgt::Features::TIMESTAMP_QUERY_INSIDE_ENCODERS)?;
364
365        let mut cmd_buf_data = cmd_buf.data.lock();
366        let cmd_buf_data = cmd_buf_data.as_mut().unwrap();
367
368        #[cfg(feature = "trace")]
369        if let Some(ref mut list) = cmd_buf_data.commands {
370            list.push(TraceCommand::WriteTimestamp {
371                query_set_id,
372                query_index,
373            });
374        }
375
376        let encoder = &mut cmd_buf_data.encoder;
377        let tracker = &mut cmd_buf_data.trackers;
378
379        let raw_encoder = encoder.open()?;
380
381        let query_set_guard = hub.query_sets.read();
382        let query_set = tracker
383            .query_sets
384            .add_single(&*query_set_guard, query_set_id)
385            .ok_or(QueryError::InvalidQuerySet(query_set_id))?;
386
387        query_set.validate_and_write_timestamp(raw_encoder, query_set_id, query_index, None)?;
388
389        Ok(())
390    }
391
392    pub fn command_encoder_resolve_query_set<A: HalApi>(
393        &self,
394        command_encoder_id: id::CommandEncoderId,
395        query_set_id: id::QuerySetId,
396        start_query: u32,
397        query_count: u32,
398        destination: id::BufferId,
399        destination_offset: BufferAddress,
400    ) -> Result<(), QueryError> {
401        let hub = A::hub(self);
402
403        let cmd_buf = CommandBuffer::get_encoder(hub, command_encoder_id)?;
404        let mut cmd_buf_data = cmd_buf.data.lock();
405        let cmd_buf_data = cmd_buf_data.as_mut().unwrap();
406
407        #[cfg(feature = "trace")]
408        if let Some(ref mut list) = cmd_buf_data.commands {
409            list.push(TraceCommand::ResolveQuerySet {
410                query_set_id,
411                start_query,
412                query_count,
413                destination,
414                destination_offset,
415            });
416        }
417
418        let encoder = &mut cmd_buf_data.encoder;
419        let tracker = &mut cmd_buf_data.trackers;
420        let buffer_memory_init_actions = &mut cmd_buf_data.buffer_memory_init_actions;
421        let raw_encoder = encoder.open()?;
422
423        if destination_offset % wgt::QUERY_RESOLVE_BUFFER_ALIGNMENT != 0 {
424            return Err(QueryError::Resolve(ResolveError::BufferOffsetAlignment));
425        }
426        let query_set_guard = hub.query_sets.read();
427        let query_set = tracker
428            .query_sets
429            .add_single(&*query_set_guard, query_set_id)
430            .ok_or(QueryError::InvalidQuerySet(query_set_id))?;
431
432        if query_set.device.as_info().id() != cmd_buf.device.as_info().id() {
433            return Err(DeviceError::WrongDevice.into());
434        }
435
436        let (dst_buffer, dst_pending) = {
437            let buffer_guard = hub.buffers.read();
438            let dst_buffer = buffer_guard
439                .get(destination)
440                .map_err(|_| QueryError::InvalidBuffer(destination))?;
441
442            if dst_buffer.device.as_info().id() != cmd_buf.device.as_info().id() {
443                return Err(DeviceError::WrongDevice.into());
444            }
445
446            tracker
447                .buffers
448                .set_single(dst_buffer, hal::BufferUses::COPY_DST)
449                .ok_or(QueryError::InvalidBuffer(destination))?
450        };
451
452        let snatch_guard = dst_buffer.device.snatchable_lock.read();
453
454        let dst_barrier = dst_pending.map(|pending| pending.into_hal(&dst_buffer, &snatch_guard));
455
456        if !dst_buffer.usage.contains(wgt::BufferUsages::QUERY_RESOLVE) {
457            return Err(ResolveError::MissingBufferUsage.into());
458        }
459
460        let end_query = start_query + query_count;
461        if end_query > query_set.desc.count {
462            return Err(ResolveError::QueryOverrun {
463                start_query,
464                end_query,
465                query_set_size: query_set.desc.count,
466            }
467            .into());
468        }
469
470        let elements_per_query = match query_set.desc.ty {
471            wgt::QueryType::Occlusion => 1,
472            wgt::QueryType::PipelineStatistics(ps) => ps.bits().count_ones(),
473            wgt::QueryType::Timestamp => 1,
474        };
475        let stride = elements_per_query * wgt::QUERY_SIZE;
476        let bytes_used = (stride * query_count) as BufferAddress;
477
478        let buffer_start_offset = destination_offset;
479        let buffer_end_offset = buffer_start_offset + bytes_used;
480
481        if buffer_end_offset > dst_buffer.size {
482            return Err(ResolveError::BufferOverrun {
483                start_query,
484                end_query,
485                stride,
486                buffer_size: dst_buffer.size,
487                buffer_start_offset,
488                buffer_end_offset,
489            }
490            .into());
491        }
492
493        // TODO(https://github.com/gfx-rs/wgpu/issues/3993): Need to track initialization state.
494        buffer_memory_init_actions.extend(dst_buffer.initialization_status.read().create_action(
495            &dst_buffer,
496            buffer_start_offset..buffer_end_offset,
497            MemoryInitKind::ImplicitlyInitialized,
498        ));
499
500        let raw_dst_buffer = dst_buffer
501            .raw(&snatch_guard)
502            .ok_or(QueryError::InvalidBuffer(destination))?;
503
504        unsafe {
505            raw_encoder.transition_buffers(dst_barrier.into_iter());
506            raw_encoder.copy_query_results(
507                query_set.raw(),
508                start_query..end_query,
509                raw_dst_buffer,
510                destination_offset,
511                wgt::BufferSize::new_unchecked(stride as u64),
512            );
513        }
514
515        Ok(())
516    }
517}