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 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 (Some(..), true) => {}
62 (Some(start), false) => {
64 run_start = None;
65 unsafe { raw_encoder.reset_queries(query_set.raw(), start..idx as u32) };
66 }
67 (None, true) => {
69 run_start = Some(idx as u32);
70 }
71 (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#[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#[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#[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 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 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 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 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 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 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}