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 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 (Some(..), true) => {}
68 (Some(start), false) => {
70 run_start = None;
71 unsafe { raw_encoder.reset_queries(query_set.raw(), start..idx as u32) };
72 }
73 (None, true) => {
75 run_start = Some(idx as u32);
76 }
77 (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#[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#[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#[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 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 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 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 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 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 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 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}