wgpu_core/command/
query.rs

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