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