1use alloc::{string::String, sync::Arc, vec::Vec};
2use core::ops::Range;
3
4use crate::{
5 api_log,
6 command::{encoder::EncodingState, ArcCommand, EncoderStateError},
7 device::{DeviceError, MissingFeatures},
8 get_lowest_common_denom, hal_label,
9 init_tracker::{MemoryInitKind, TextureInitRange},
10 resource::{
11 Buffer, DestroyedResourceError, InvalidOrDestroyedResourceError, InvalidResourceError,
12 Labeled, MissingBufferUsageError, ParentDevice, RawResourceAccess, ResourceErrorIdent,
13 Texture, TextureClearMode,
14 },
15 snatch::SnatchGuard,
16 track::TextureTrackerSetSingle,
17};
18
19use thiserror::Error;
20use wgt::{
21 error::{ErrorType, WebGpuError},
22 math::align_to,
23 BufferAddress, BufferUsages, ImageSubresourceRange, TextureAspect, TextureSelector,
24};
25
26#[derive(Clone, Debug, Error)]
28#[non_exhaustive]
29pub enum ClearError {
30 #[error(transparent)]
31 DestroyedResource(#[from] DestroyedResourceError),
32 #[error(transparent)]
33 MissingFeatures(#[from] MissingFeatures),
34 #[error("{0} can not be cleared")]
35 NoValidTextureClearMode(ResourceErrorIdent),
36 #[error("Buffer clear size {0:?} is not a multiple of `COPY_BUFFER_ALIGNMENT`")]
37 UnalignedFillSize(BufferAddress),
38 #[error("Buffer offset {0:?} is not a multiple of `COPY_BUFFER_ALIGNMENT`")]
39 UnalignedBufferOffset(BufferAddress),
40 #[error("Clear starts at offset {start_offset} with size of {requested_size}, but these added together exceed `u64::MAX`")]
41 OffsetPlusSizeExceeds64BitBounds {
42 start_offset: BufferAddress,
43 requested_size: BufferAddress,
44 },
45 #[error("Clear of {start_offset}..{end_offset} would end up overrunning the bounds of the buffer of size {buffer_size}")]
46 BufferOverrun {
47 start_offset: BufferAddress,
48 end_offset: BufferAddress,
49 buffer_size: BufferAddress,
50 },
51 #[error(transparent)]
52 MissingBufferUsage(#[from] MissingBufferUsageError),
53 #[error("Texture lacks the aspects that were specified in the image subresource range. Texture with format {texture_format:?}, specified was {subresource_range_aspects:?}")]
54 MissingTextureAspect {
55 texture_format: wgt::TextureFormat,
56 subresource_range_aspects: TextureAspect,
57 },
58 #[error("Image subresource level range is outside of the texture's level range. texture range is {texture_level_range:?}, \
59whereas subesource range specified start {subresource_base_mip_level} and count {subresource_mip_level_count:?}")]
60 InvalidTextureLevelRange {
61 texture_level_range: Range<u32>,
62 subresource_base_mip_level: u32,
63 subresource_mip_level_count: Option<u32>,
64 },
65 #[error("Image subresource layer range is outside of the texture's layer range. texture range is {texture_layer_range:?}, \
66whereas subesource range specified start {subresource_base_array_layer} and count {subresource_array_layer_count:?}")]
67 InvalidTextureLayerRange {
68 texture_layer_range: Range<u32>,
69 subresource_base_array_layer: u32,
70 subresource_array_layer_count: Option<u32>,
71 },
72 #[error(transparent)]
73 Device(#[from] DeviceError),
74 #[error(transparent)]
75 EncoderState(#[from] EncoderStateError),
76 #[error(transparent)]
77 InvalidResource(#[from] InvalidResourceError),
78}
79
80impl From<InvalidOrDestroyedResourceError> for ClearError {
81 fn from(value: InvalidOrDestroyedResourceError) -> Self {
82 match value {
83 InvalidOrDestroyedResourceError::InvalidResource(e) => Self::InvalidResource(e),
84 InvalidOrDestroyedResourceError::DestroyedResource(e) => Self::DestroyedResource(e),
85 }
86 }
87}
88
89impl WebGpuError for ClearError {
90 fn webgpu_error_type(&self) -> ErrorType {
91 match self {
92 Self::DestroyedResource(e) => e.webgpu_error_type(),
93 Self::MissingFeatures(e) => e.webgpu_error_type(),
94 Self::MissingBufferUsage(e) => e.webgpu_error_type(),
95 Self::Device(e) => e.webgpu_error_type(),
96 Self::EncoderState(e) => e.webgpu_error_type(),
97 Self::InvalidResource(e) => e.webgpu_error_type(),
98 Self::NoValidTextureClearMode(..)
99 | Self::UnalignedFillSize(..)
100 | Self::UnalignedBufferOffset(..)
101 | Self::OffsetPlusSizeExceeds64BitBounds { .. }
102 | Self::BufferOverrun { .. }
103 | Self::MissingTextureAspect { .. }
104 | Self::InvalidTextureLevelRange { .. }
105 | Self::InvalidTextureLayerRange { .. } => ErrorType::Validation,
106 }
107 }
108}
109
110impl super::CommandEncoder {
111 fn clear_buffer_inner(
112 self: &Arc<Self>,
113 dst: Arc<Buffer>,
114 offset: BufferAddress,
115 size: Option<BufferAddress>,
116 ) -> Result<(), EncoderStateError> {
117 profiling::scope!("CommandEncoder::clear_buffer");
118 api_log!("CommandEncoder::clear_buffer {:?}", Arc::as_ptr(&dst));
119
120 let mut cmd_buf_data = self.data.lock();
121
122 cmd_buf_data.push_with(|| -> Result<_, ClearError> {
123 dst.check_is_valid()?;
124 Ok(ArcCommand::ClearBuffer { dst, offset, size })
125 })
126 }
127
128 pub fn clear_buffer(
129 self: &Arc<Self>,
130 dst: Arc<Buffer>,
131 offset: BufferAddress,
132 size: Option<BufferAddress>,
133 ) {
134 if let Err(err) = self.clear_buffer_inner(dst, offset, size) {
135 self.device
136 .handle_error(err, Some(self.label()), "CommandEncoder::clear_buffer");
137 }
138 }
139
140 fn clear_texture_inner(
141 self: &Arc<Self>,
142 dst: Arc<Texture>,
143 subresource_range: &ImageSubresourceRange,
144 ) -> Result<(), EncoderStateError> {
145 profiling::scope!("CommandEncoder::clear_texture");
146 api_log!("CommandEncoder::clear_texture {:?}", Arc::as_ptr(&dst));
147
148 let mut cmd_buf_data = self.data.lock();
149
150 cmd_buf_data.push_with(|| -> Result<_, ClearError> {
151 dst.check_valid()?;
152 Ok(ArcCommand::ClearTexture {
153 dst,
154 subresource_range: *subresource_range,
155 })
156 })
157 }
158
159 pub fn clear_texture(
160 self: &Arc<Self>,
161 dst: Arc<Texture>,
162 subresource_range: &ImageSubresourceRange,
163 ) {
164 if let Err(err) = self.clear_texture_inner(dst, subresource_range) {
165 self.device
166 .handle_error(err, Some(self.label()), "CommandEncoder::clear_texture");
167 }
168 }
169}
170
171pub(super) fn clear_buffer(
172 state: &mut EncodingState,
173 dst_buffer: Arc<Buffer>,
174 offset: BufferAddress,
175 size: Option<BufferAddress>,
176) -> Result<(), ClearError> {
177 dst_buffer.same_device(state.device)?;
178
179 let dst_pending = state
180 .tracker
181 .buffers
182 .set_single(&dst_buffer, wgt::BufferUses::COPY_DST);
183
184 let dst_raw = dst_buffer.try_raw(state.snatch_guard)?;
185 dst_buffer.check_usage(BufferUsages::COPY_DST)?;
186
187 if !offset.is_multiple_of(wgt::COPY_BUFFER_ALIGNMENT) {
189 return Err(ClearError::UnalignedBufferOffset(offset));
190 }
191
192 let size = size.unwrap_or(dst_buffer.size.saturating_sub(offset));
193 if !size.is_multiple_of(wgt::COPY_BUFFER_ALIGNMENT) {
194 return Err(ClearError::UnalignedFillSize(size));
195 }
196 let end_offset =
197 offset
198 .checked_add(size)
199 .ok_or(ClearError::OffsetPlusSizeExceeds64BitBounds {
200 start_offset: offset,
201 requested_size: size,
202 })?;
203 if end_offset > dst_buffer.size {
204 return Err(ClearError::BufferOverrun {
205 start_offset: offset,
206 end_offset,
207 buffer_size: dst_buffer.size,
208 });
209 }
210
211 if offset == end_offset {
214 log::trace!("Ignoring fill_buffer of size 0");
215 return Ok(());
216 }
217
218 state
220 .buffer_memory_init_actions
221 .extend(dst_buffer.initialization_status.read().create_action(
222 &dst_buffer,
223 offset..end_offset,
224 MemoryInitKind::ImplicitlyInitialized,
225 ));
226
227 let dst_barrier = dst_pending.map(|pending| pending.into_hal(&dst_buffer, state.snatch_guard));
229 unsafe {
230 state.raw_encoder.transition_buffers(dst_barrier.as_slice());
231 state.raw_encoder.clear_buffer(dst_raw, offset..end_offset);
232 }
233
234 Ok(())
235}
236
237pub(super) fn clear_texture_cmd(
245 state: &mut EncodingState,
246 dst_texture: Arc<Texture>,
247 subresource_range: &ImageSubresourceRange,
248) -> Result<(), ClearError> {
249 dst_texture.same_device(state.device)?;
250 state
251 .device
252 .require_features(wgt::Features::CLEAR_TEXTURE)?;
253
254 let clear_aspects = hal::FormatAspects::new(dst_texture.desc.format, subresource_range.aspect);
256 if clear_aspects.is_empty() {
257 return Err(ClearError::MissingTextureAspect {
258 texture_format: dst_texture.desc.format,
259 subresource_range_aspects: subresource_range.aspect,
260 });
261 };
262
263 let subresource_mip_range = subresource_range.mip_range(dst_texture.full_range.mips.end);
265 if dst_texture.full_range.mips.start > subresource_mip_range.start
266 || dst_texture.full_range.mips.end < subresource_mip_range.end
267 {
268 return Err(ClearError::InvalidTextureLevelRange {
269 texture_level_range: dst_texture.full_range.mips.clone(),
270 subresource_base_mip_level: subresource_range.base_mip_level,
271 subresource_mip_level_count: subresource_range.mip_level_count,
272 });
273 }
274 let subresource_layer_range = subresource_range.layer_range(dst_texture.full_range.layers.end);
276 if dst_texture.full_range.layers.start > subresource_layer_range.start
277 || dst_texture.full_range.layers.end < subresource_layer_range.end
278 {
279 return Err(ClearError::InvalidTextureLayerRange {
280 texture_layer_range: dst_texture.full_range.layers.clone(),
281 subresource_base_array_layer: subresource_range.base_array_layer,
282 subresource_array_layer_count: subresource_range.array_layer_count,
283 });
284 }
285
286 clear_texture(
287 &dst_texture,
288 TextureInitRange {
289 mip_range: subresource_mip_range,
290 layer_range: subresource_layer_range,
291 },
292 None,
293 state.raw_encoder,
294 &mut state.tracker.textures,
295 &state.device.alignments,
296 state.device.zero_buffer.as_ref(),
297 state.snatch_guard,
298 state.device.instance_flags,
299 )?;
300
301 Ok(())
302}
303
304pub(crate) fn clear_texture<T: TextureTrackerSetSingle>(
317 dst_texture: &Arc<Texture>,
318 range: TextureInitRange,
319 depth_slice: Option<u32>,
320 encoder: &mut dyn hal::DynCommandEncoder,
321 texture_tracker: &mut T,
322 alignments: &hal::Alignments,
323 zero_buffer: &dyn hal::DynBuffer,
324 snatch_guard: &SnatchGuard<'_>,
325 instance_flags: wgt::InstanceFlags,
326) -> Result<(), ClearError> {
327 let dst_raw = dst_texture.try_inner(snatch_guard)?.raw();
328
329 let clear_usage = match *dst_texture.clear_mode.read() {
331 TextureClearMode::BufferCopy => wgt::TextureUses::COPY_DST,
332 TextureClearMode::RenderPass {
333 is_color: false, ..
334 } => wgt::TextureUses::DEPTH_WRITE | wgt::TextureUses::STENCIL_WRITE,
335 TextureClearMode::Surface { .. } | TextureClearMode::RenderPass { is_color: true, .. } => {
336 wgt::TextureUses::COLOR_TARGET
337 }
338 TextureClearMode::None => {
339 return Err(ClearError::NoValidTextureClearMode(
340 dst_texture.error_ident(),
341 ));
342 }
343 };
344
345 let selector = TextureSelector {
346 mips: range.mip_range.clone(),
347 layers: range.layer_range.clone(),
348 };
349
350 let dst_barrier = texture_tracker
364 .set_single(dst_texture, selector, clear_usage)
365 .map(|pending| pending.into_hal(dst_raw))
366 .collect::<Vec<_>>();
367 unsafe {
368 encoder.transition_textures(&dst_barrier);
369 }
370
371 let clear_mode = dst_texture.clear_mode.read();
373 match *clear_mode {
374 TextureClearMode::BufferCopy => clear_texture_via_buffer_copies(
375 &dst_texture.desc,
376 alignments,
377 zero_buffer,
378 range,
379 depth_slice,
380 encoder,
381 dst_raw,
382 ),
383 TextureClearMode::Surface { .. } => {
384 drop(clear_mode);
385 clear_texture_via_render_passes(dst_texture, range, true, encoder, instance_flags)?
386 }
387 TextureClearMode::RenderPass { is_color, .. } => {
388 drop(clear_mode);
389 clear_texture_via_render_passes(dst_texture, range, is_color, encoder, instance_flags)?
390 }
391 TextureClearMode::None => {
392 return Err(ClearError::NoValidTextureClearMode(
393 dst_texture.error_ident(),
394 ));
395 }
396 }
397 Ok(())
398}
399
400fn clear_texture_via_buffer_copies(
401 texture_desc: &wgt::TextureDescriptor<String, Vec<wgt::TextureFormat>>,
402 alignments: &hal::Alignments,
403 zero_buffer: &dyn hal::DynBuffer, range: TextureInitRange,
405 depth_slice: Option<u32>,
406 encoder: &mut dyn hal::DynCommandEncoder,
407 dst_raw: &dyn hal::DynTexture,
408) {
409 assert!(!texture_desc.format.is_depth_stencil_format());
412 assert!(
413 depth_slice.is_none() || texture_desc.dimension == wgt::TextureDimension::D3,
414 "depth_slice is only applicable to 3D textures",
415 );
416 assert!(
417 depth_slice.is_none() || range.mip_range.len() == 1,
418 "must target a single depth slice when clearing a mip level",
419 );
420
421 if texture_desc.format == wgt::TextureFormat::NV12
422 || texture_desc.format == wgt::TextureFormat::P010
423 {
424 return;
426 }
427
428 let mut zero_buffer_copy_regions = Vec::new();
430 let buffer_copy_pitch = alignments.buffer_copy_pitch.get() as u32;
431 let (block_width, block_height) = texture_desc.format.block_dimensions();
432 let block_size = texture_desc.format.block_copy_size(None).unwrap();
433
434 let bytes_per_row_alignment = get_lowest_common_denom(buffer_copy_pitch, block_size);
435
436 for mip_level in range.mip_range {
437 let mut mip_size = texture_desc.mip_level_size(mip_level).unwrap();
438 mip_size.width = align_to(mip_size.width, block_width);
440 mip_size.height = align_to(mip_size.height, block_height);
441
442 let bytes_per_row = align_to(
443 mip_size.width / block_width * block_size,
444 bytes_per_row_alignment,
445 );
446
447 let max_rows_per_copy = crate::device::ZERO_BUFFER_SIZE as u32 / bytes_per_row;
448 let max_rows_per_copy = max_rows_per_copy / block_height * block_height;
450 assert!(
451 max_rows_per_copy > 0,
452 "Zero buffer size is too small to fill a single row \
453 of a texture with format {:?} and desc {:?}",
454 texture_desc.format,
455 texture_desc.size
456 );
457
458 let z_range = if let Some(depth_slice) = depth_slice {
459 depth_slice..depth_slice + 1
460 } else if texture_desc.dimension == wgt::TextureDimension::D3 {
461 0..mip_size.depth_or_array_layers
462 } else {
463 0..1
464 };
465
466 for array_layer in range.layer_range.clone() {
467 for z in z_range.clone() {
469 let mut num_rows_left = mip_size.height;
472 while num_rows_left > 0 {
473 let num_rows = num_rows_left.min(max_rows_per_copy);
474
475 zero_buffer_copy_regions.push(hal::BufferTextureCopy {
476 buffer_layout: wgt::TexelCopyBufferLayout {
477 offset: 0,
478 bytes_per_row: Some(bytes_per_row),
479 rows_per_image: None,
480 },
481 texture_base: hal::TextureCopyBase {
482 mip_level,
483 array_layer,
484 origin: wgt::Origin3d {
485 x: 0, y: mip_size.height - num_rows_left,
487 z,
488 },
489 aspect: hal::FormatAspects::COLOR,
490 },
491 size: hal::CopyExtent {
492 width: mip_size.width, height: num_rows,
494 depth: 1, },
496 });
497
498 num_rows_left -= num_rows;
499 }
500 }
501 }
502 }
503
504 unsafe {
505 encoder.copy_buffer_to_texture(zero_buffer, dst_raw, &zero_buffer_copy_regions);
506 }
507}
508
509fn clear_texture_via_render_passes(
510 dst_texture: &Texture,
511 range: TextureInitRange,
512 is_color: bool,
513 encoder: &mut dyn hal::DynCommandEncoder,
514 instance_flags: wgt::InstanceFlags,
515) -> Result<(), ClearError> {
516 assert_eq!(dst_texture.desc.dimension, wgt::TextureDimension::D2);
517
518 let extent_base = wgt::Extent3d {
519 width: dst_texture.desc.size.width,
520 height: dst_texture.desc.size.height,
521 depth_or_array_layers: 1, };
523
524 let clear_mode = dst_texture.clear_mode.read();
525
526 for mip_level in range.mip_range {
527 let extent = extent_base.mip_level_size(mip_level, dst_texture.desc.dimension);
528 for depth_or_layer in range.layer_range.clone() {
529 let color_attachments_tmp;
530 let (color_attachments, depth_stencil_attachment) = if is_color {
531 color_attachments_tmp = [Some(hal::ColorAttachment {
532 target: hal::Attachment {
533 view: Texture::get_clear_view(
534 &clear_mode,
535 &dst_texture.desc,
536 mip_level,
537 depth_or_layer,
538 ),
539 usage: wgt::TextureUses::COLOR_TARGET,
540 },
541 depth_slice: None,
542 resolve_target: None,
543 ops: hal::AttachmentOps::STORE | hal::AttachmentOps::LOAD_CLEAR,
544 clear_value: wgt::Color::TRANSPARENT,
545 })];
546 (&color_attachments_tmp[..], None)
547 } else {
548 (
549 &[][..],
550 Some(hal::DepthStencilAttachment {
551 target: hal::Attachment {
552 view: Texture::get_clear_view(
553 &clear_mode,
554 &dst_texture.desc,
555 mip_level,
556 depth_or_layer,
557 ),
558 usage: wgt::TextureUses::DEPTH_WRITE | wgt::TextureUses::STENCIL_WRITE,
559 },
560 depth_ops: hal::AttachmentOps::STORE | hal::AttachmentOps::LOAD_CLEAR,
561 stencil_ops: hal::AttachmentOps::STORE | hal::AttachmentOps::LOAD_CLEAR,
562 clear_value: (0.0, 0),
563 depth_read_only: false,
564 stencil_read_only: false,
565 }),
566 )
567 };
568 unsafe {
569 encoder
570 .begin_render_pass(&hal::RenderPassDescriptor {
571 label: hal_label(
572 Some("(wgpu internal) clear_texture clear pass"),
573 instance_flags,
574 ),
575 extent,
576 sample_count: dst_texture.desc.sample_count,
577 color_attachments,
578 depth_stencil_attachment,
579 multiview_mask: None,
580 timestamp_writes: None,
581 occlusion_query_set: None,
582 })
583 .map_err(|e| dst_texture.device.handle_hal_error(e))?;
584 encoder.end_render_pass();
585 }
586 }
587 }
588
589 Ok(())
590}