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("Unable to selectively clear aspect {aspect:?} of format {format:?}. All aspects must be cleared.")]
59 InvalidClearAspect {
60 format: wgt::TextureFormat,
61 aspect: TextureAspect,
62 },
63 #[error("Image subresource level range is outside of the texture's level range. texture range is {texture_level_range:?}, \
64whereas subesource range specified start {subresource_base_mip_level} and count {subresource_mip_level_count:?}")]
65 InvalidTextureLevelRange {
66 texture_level_range: Range<u32>,
67 subresource_base_mip_level: u32,
68 subresource_mip_level_count: Option<u32>,
69 },
70 #[error("Image subresource layer range is outside of the texture's layer range. texture range is {texture_layer_range:?}, \
71whereas subesource range specified start {subresource_base_array_layer} and count {subresource_array_layer_count:?}")]
72 InvalidTextureLayerRange {
73 texture_layer_range: Range<u32>,
74 subresource_base_array_layer: u32,
75 subresource_array_layer_count: Option<u32>,
76 },
77 #[error(transparent)]
78 Device(#[from] DeviceError),
79 #[error(transparent)]
80 EncoderState(#[from] EncoderStateError),
81 #[error(transparent)]
82 InvalidResource(#[from] InvalidResourceError),
83}
84
85impl From<InvalidOrDestroyedResourceError> for ClearError {
86 fn from(value: InvalidOrDestroyedResourceError) -> Self {
87 match value {
88 InvalidOrDestroyedResourceError::InvalidResource(e) => Self::InvalidResource(e),
89 InvalidOrDestroyedResourceError::DestroyedResource(e) => Self::DestroyedResource(e),
90 }
91 }
92}
93
94impl WebGpuError for ClearError {
95 fn webgpu_error_type(&self) -> ErrorType {
96 match self {
97 Self::DestroyedResource(e) => e.webgpu_error_type(),
98 Self::MissingFeatures(e) => e.webgpu_error_type(),
99 Self::MissingBufferUsage(e) => e.webgpu_error_type(),
100 Self::Device(e) => e.webgpu_error_type(),
101 Self::EncoderState(e) => e.webgpu_error_type(),
102 Self::InvalidResource(e) => e.webgpu_error_type(),
103 Self::NoValidTextureClearMode(..)
104 | Self::UnalignedFillSize(..)
105 | Self::UnalignedBufferOffset(..)
106 | Self::OffsetPlusSizeExceeds64BitBounds { .. }
107 | Self::BufferOverrun { .. }
108 | Self::MissingTextureAspect { .. }
109 | Self::InvalidClearAspect { .. }
110 | Self::InvalidTextureLevelRange { .. }
111 | Self::InvalidTextureLayerRange { .. } => ErrorType::Validation,
112 }
113 }
114}
115
116impl super::CommandEncoder {
117 fn clear_buffer_inner(
118 self: &Arc<Self>,
119 dst: Arc<Buffer>,
120 offset: BufferAddress,
121 size: Option<BufferAddress>,
122 ) -> Result<(), EncoderStateError> {
123 profiling::scope!("CommandEncoder::clear_buffer");
124 api_log!("CommandEncoder::clear_buffer {:?}", Arc::as_ptr(&dst));
125
126 let mut cmd_buf_data = self.data.lock();
127
128 cmd_buf_data.push_with(|| -> Result<_, ClearError> {
129 dst.check_is_valid()?;
130 Ok(ArcCommand::ClearBuffer { dst, offset, size })
131 })
132 }
133
134 pub fn clear_buffer(
135 self: &Arc<Self>,
136 dst: Arc<Buffer>,
137 offset: BufferAddress,
138 size: Option<BufferAddress>,
139 ) {
140 if let Err(err) = self.clear_buffer_inner(dst, offset, size) {
141 self.device
142 .handle_error(err, Some(self.label()), "CommandEncoder::clear_buffer");
143 }
144 }
145
146 fn clear_texture_inner(
147 self: &Arc<Self>,
148 dst: Arc<Texture>,
149 subresource_range: &ImageSubresourceRange,
150 ) -> Result<(), EncoderStateError> {
151 profiling::scope!("CommandEncoder::clear_texture");
152 api_log!("CommandEncoder::clear_texture {:?}", Arc::as_ptr(&dst));
153
154 let mut cmd_buf_data = self.data.lock();
155
156 cmd_buf_data.push_with(|| -> Result<_, ClearError> {
157 dst.check_valid()?;
158 Ok(ArcCommand::ClearTexture {
159 dst,
160 subresource_range: *subresource_range,
161 })
162 })
163 }
164
165 pub fn clear_texture(
166 self: &Arc<Self>,
167 dst: Arc<Texture>,
168 subresource_range: &ImageSubresourceRange,
169 ) {
170 if let Err(err) = self.clear_texture_inner(dst, subresource_range) {
171 self.device
172 .handle_error(err, Some(self.label()), "CommandEncoder::clear_texture");
173 }
174 }
175}
176
177pub(super) fn clear_buffer(
178 state: &mut EncodingState,
179 dst_buffer: Arc<Buffer>,
180 offset: BufferAddress,
181 size: Option<BufferAddress>,
182) -> Result<(), ClearError> {
183 dst_buffer.same_device(state.device)?;
184
185 let dst_pending = state
186 .tracker
187 .buffers
188 .set_single(&dst_buffer, wgt::BufferUses::COPY_DST);
189
190 let dst_raw = dst_buffer.try_raw(state.snatch_guard)?;
191 dst_buffer.check_usage(BufferUsages::COPY_DST)?;
192
193 if !offset.is_multiple_of(wgt::COPY_BUFFER_ALIGNMENT) {
195 return Err(ClearError::UnalignedBufferOffset(offset));
196 }
197
198 let size = size.unwrap_or(dst_buffer.size.saturating_sub(offset));
199 if !size.is_multiple_of(wgt::COPY_BUFFER_ALIGNMENT) {
200 return Err(ClearError::UnalignedFillSize(size));
201 }
202 let end_offset =
203 offset
204 .checked_add(size)
205 .ok_or(ClearError::OffsetPlusSizeExceeds64BitBounds {
206 start_offset: offset,
207 requested_size: size,
208 })?;
209 if end_offset > dst_buffer.size {
210 return Err(ClearError::BufferOverrun {
211 start_offset: offset,
212 end_offset,
213 buffer_size: dst_buffer.size,
214 });
215 }
216
217 if offset == end_offset {
220 log::trace!("Ignoring fill_buffer of size 0");
221 return Ok(());
222 }
223
224 state
226 .buffer_memory_init_actions
227 .extend(dst_buffer.initialization_status.read().create_action(
228 &dst_buffer,
229 offset..end_offset,
230 MemoryInitKind::ImplicitlyInitialized,
231 ));
232
233 let dst_barrier = dst_pending.map(|pending| pending.into_hal(&dst_buffer, state.snatch_guard));
235 unsafe {
236 state.raw_encoder.transition_buffers(dst_barrier.as_slice());
237 state.raw_encoder.clear_buffer(dst_raw, offset..end_offset);
238 }
239
240 Ok(())
241}
242
243pub(super) fn clear_texture_cmd(
251 state: &mut EncodingState,
252 dst_texture: Arc<Texture>,
253 subresource_range: &ImageSubresourceRange,
254) -> Result<(), ClearError> {
255 dst_texture.same_device(state.device)?;
256 state
257 .device
258 .require_features(wgt::Features::CLEAR_TEXTURE)?;
259
260 let clear_aspects = hal::FormatAspects::new(dst_texture.desc.format, subresource_range.aspect);
262 let all_aspects = hal::FormatAspects::new(dst_texture.desc.format, TextureAspect::All);
263 if clear_aspects.is_empty() {
264 return Err(ClearError::MissingTextureAspect {
265 texture_format: dst_texture.desc.format,
266 subresource_range_aspects: subresource_range.aspect,
267 });
268 }
269 if clear_aspects != all_aspects {
270 return Err(ClearError::InvalidClearAspect {
271 format: dst_texture.desc.format,
272 aspect: subresource_range.aspect,
273 });
274 }
275
276 let subresource_mip_range = subresource_range.mip_range(dst_texture.full_range.mips.end);
278 if dst_texture.full_range.mips.start > subresource_mip_range.start
279 || dst_texture.full_range.mips.end < subresource_mip_range.end
280 {
281 return Err(ClearError::InvalidTextureLevelRange {
282 texture_level_range: dst_texture.full_range.mips.clone(),
283 subresource_base_mip_level: subresource_range.base_mip_level,
284 subresource_mip_level_count: subresource_range.mip_level_count,
285 });
286 }
287 let subresource_layer_range = subresource_range.layer_range(dst_texture.full_range.layers.end);
289 if dst_texture.full_range.layers.start > subresource_layer_range.start
290 || dst_texture.full_range.layers.end < subresource_layer_range.end
291 {
292 return Err(ClearError::InvalidTextureLayerRange {
293 texture_layer_range: dst_texture.full_range.layers.clone(),
294 subresource_base_array_layer: subresource_range.base_array_layer,
295 subresource_array_layer_count: subresource_range.array_layer_count,
296 });
297 }
298
299 clear_texture(
300 &dst_texture,
301 TextureInitRange {
302 mip_range: subresource_mip_range,
303 layer_range: subresource_layer_range,
304 },
305 None,
306 state.raw_encoder,
307 &mut state.tracker.textures,
308 &state.device.alignments,
309 state.device.zero_buffer.as_ref(),
310 state.snatch_guard,
311 state.device.instance_flags,
312 )?;
313
314 Ok(())
315}
316
317pub(crate) fn clear_texture<T: TextureTrackerSetSingle>(
330 dst_texture: &Arc<Texture>,
331 range: TextureInitRange,
332 depth_slice: Option<u32>,
333 encoder: &mut dyn hal::DynCommandEncoder,
334 texture_tracker: &mut T,
335 alignments: &hal::Alignments,
336 zero_buffer: &dyn hal::DynBuffer,
337 snatch_guard: &SnatchGuard<'_>,
338 instance_flags: wgt::InstanceFlags,
339) -> Result<(), ClearError> {
340 let dst_raw = dst_texture.try_inner(snatch_guard)?.raw();
341
342 let clear_usage = match *dst_texture.clear_mode.read() {
344 TextureClearMode::BufferCopy => wgt::TextureUses::COPY_DST,
345 TextureClearMode::RenderPass {
346 is_color: false, ..
347 } => wgt::TextureUses::DEPTH_WRITE | wgt::TextureUses::STENCIL_WRITE,
348 TextureClearMode::Surface { .. } | TextureClearMode::RenderPass { is_color: true, .. } => {
349 wgt::TextureUses::COLOR_TARGET
350 }
351 TextureClearMode::None => {
352 return Err(ClearError::NoValidTextureClearMode(
353 dst_texture.error_ident(),
354 ));
355 }
356 };
357
358 let selector = TextureSelector {
359 mips: range.mip_range.clone(),
360 layers: range.layer_range.clone(),
361 };
362
363 let dst_barrier = texture_tracker
377 .set_single(dst_texture, selector, clear_usage)
378 .map(|pending| pending.into_hal(dst_raw))
379 .collect::<Vec<_>>();
380 unsafe {
381 encoder.transition_textures(&dst_barrier);
382 }
383
384 let clear_mode = dst_texture.clear_mode.read();
386 match *clear_mode {
387 TextureClearMode::BufferCopy => clear_texture_via_buffer_copies(
388 &dst_texture.desc,
389 alignments,
390 zero_buffer,
391 range,
392 depth_slice,
393 encoder,
394 dst_raw,
395 ),
396 TextureClearMode::Surface { .. } => {
397 drop(clear_mode);
398 clear_texture_via_render_passes(dst_texture, range, true, encoder, instance_flags)?
399 }
400 TextureClearMode::RenderPass { is_color, .. } => {
401 drop(clear_mode);
402 clear_texture_via_render_passes(dst_texture, range, is_color, encoder, instance_flags)?
403 }
404 TextureClearMode::None => {
405 return Err(ClearError::NoValidTextureClearMode(
406 dst_texture.error_ident(),
407 ));
408 }
409 }
410 Ok(())
411}
412
413fn clear_texture_via_buffer_copies(
414 texture_desc: &wgt::TextureDescriptor<String, Vec<wgt::TextureFormat>>,
415 alignments: &hal::Alignments,
416 zero_buffer: &dyn hal::DynBuffer, range: TextureInitRange,
418 depth_slice: Option<u32>,
419 encoder: &mut dyn hal::DynCommandEncoder,
420 dst_raw: &dyn hal::DynTexture,
421) {
422 assert!(!texture_desc.format.is_depth_stencil_format());
425 assert!(
426 depth_slice.is_none() || texture_desc.dimension == wgt::TextureDimension::D3,
427 "depth_slice is only applicable to 3D textures",
428 );
429 assert!(
430 depth_slice.is_none() || range.mip_range.len() == 1,
431 "must target a single depth slice when clearing a mip level",
432 );
433
434 let aspects = if texture_desc.format.is_multi_planar_format() {
435 [
436 (hal::FormatAspects::PLANE_0, TextureAspect::Plane0),
437 (hal::FormatAspects::PLANE_1, TextureAspect::Plane1),
438 (hal::FormatAspects::PLANE_2, TextureAspect::Plane2),
439 ]
440 .as_slice()
441 } else {
442 &[(hal::FormatAspects::COLOR, TextureAspect::All)]
443 };
444
445 let mut zero_buffer_copy_regions = Vec::new();
446 for &(hal_aspect, aspect) in aspects {
447 let Some(aspect_format) = texture_desc.format.aspect_specific_format(aspect) else {
448 break;
449 };
450 let buffer_copy_pitch = alignments.buffer_copy_pitch.get() as u32;
452 let (block_width, block_height) = aspect_format.block_dimensions();
453 let block_size = aspect_format.block_copy_size(None).unwrap();
454
455 let bytes_per_row_alignment = get_lowest_common_denom(buffer_copy_pitch, block_size);
456
457 for mip_level in range.mip_range.clone() {
458 let mut mip_size = texture_desc.mip_level_size(mip_level).unwrap();
459 let (w_sub, h_sub) = texture_desc.format.subsampling_factors(aspect.to_plane());
461 (mip_size.width, mip_size.height) = (mip_size.width / w_sub, mip_size.height / h_sub);
462 mip_size.width = align_to(mip_size.width, block_width);
464 mip_size.height = align_to(mip_size.height, block_height);
465
466 let bytes_per_row = align_to(
467 mip_size.width / block_width * block_size,
468 bytes_per_row_alignment,
469 );
470
471 let max_rows_per_copy = crate::device::ZERO_BUFFER_SIZE as u32 / bytes_per_row;
472 let max_rows_per_copy = max_rows_per_copy / block_height * block_height;
474 assert!(
475 max_rows_per_copy > 0,
476 "Zero buffer size is too small to fill a single row \
477 of a texture with format {:?} and desc {:?}",
478 texture_desc.format,
479 texture_desc.size
480 );
481
482 let z_range = if let Some(depth_slice) = depth_slice {
483 depth_slice..depth_slice + 1
484 } else if texture_desc.dimension == wgt::TextureDimension::D3 {
485 0..mip_size.depth_or_array_layers
486 } else {
487 0..1
488 };
489
490 for array_layer in range.layer_range.clone() {
491 for z in z_range.clone() {
493 let mut num_rows_left = mip_size.height;
496 while num_rows_left > 0 {
497 let num_rows = num_rows_left.min(max_rows_per_copy);
498
499 zero_buffer_copy_regions.push(hal::BufferTextureCopy {
500 buffer_layout: wgt::TexelCopyBufferLayout {
501 offset: 0,
502 bytes_per_row: Some(bytes_per_row),
503 rows_per_image: None,
504 },
505 texture_base: hal::TextureCopyBase {
506 mip_level,
507 array_layer,
508 origin: wgt::Origin3d {
509 x: 0, y: mip_size.height - num_rows_left,
511 z,
512 },
513 aspect: hal_aspect,
514 },
515 size: hal::CopyExtent {
516 width: mip_size.width, height: num_rows,
518 depth: 1, },
520 });
521
522 num_rows_left -= num_rows;
523 }
524 }
525 }
526 }
527 }
528
529 unsafe {
530 encoder.copy_buffer_to_texture(zero_buffer, dst_raw, &zero_buffer_copy_regions);
531 }
532}
533
534fn clear_texture_via_render_passes(
535 dst_texture: &Texture,
536 range: TextureInitRange,
537 is_color: bool,
538 encoder: &mut dyn hal::DynCommandEncoder,
539 instance_flags: wgt::InstanceFlags,
540) -> Result<(), ClearError> {
541 assert_eq!(dst_texture.desc.dimension, wgt::TextureDimension::D2);
542
543 let extent_base = wgt::Extent3d {
544 width: dst_texture.desc.size.width,
545 height: dst_texture.desc.size.height,
546 depth_or_array_layers: 1, };
548
549 let clear_mode = dst_texture.clear_mode.read();
550
551 for mip_level in range.mip_range {
552 let extent = extent_base.mip_level_size(mip_level, dst_texture.desc.dimension);
553 for depth_or_layer in range.layer_range.clone() {
554 let color_attachments_tmp;
555 let (color_attachments, depth_stencil_attachment) = if is_color {
556 color_attachments_tmp = [Some(hal::ColorAttachment {
557 target: hal::Attachment {
558 view: Texture::get_clear_view(
559 &clear_mode,
560 &dst_texture.desc,
561 mip_level,
562 depth_or_layer,
563 ),
564 usage: wgt::TextureUses::COLOR_TARGET,
565 },
566 depth_slice: None,
567 resolve_target: None,
568 ops: hal::AttachmentOps::STORE | hal::AttachmentOps::LOAD_CLEAR,
569 clear_value: wgt::Color::TRANSPARENT,
570 })];
571 (&color_attachments_tmp[..], None)
572 } else {
573 (
574 &[][..],
575 Some(hal::DepthStencilAttachment {
576 target: hal::Attachment {
577 view: Texture::get_clear_view(
578 &clear_mode,
579 &dst_texture.desc,
580 mip_level,
581 depth_or_layer,
582 ),
583 usage: wgt::TextureUses::DEPTH_WRITE | wgt::TextureUses::STENCIL_WRITE,
584 },
585 depth_ops: hal::AttachmentOps::STORE | hal::AttachmentOps::LOAD_CLEAR,
586 stencil_ops: hal::AttachmentOps::STORE | hal::AttachmentOps::LOAD_CLEAR,
587 clear_value: (0.0, 0),
588 depth_read_only: false,
589 stencil_read_only: false,
590 }),
591 )
592 };
593 unsafe {
594 encoder
595 .begin_render_pass(&hal::RenderPassDescriptor {
596 label: hal_label(
597 Some("(wgpu internal) clear_texture clear pass"),
598 instance_flags,
599 ),
600 extent,
601 sample_count: dst_texture.desc.sample_count,
602 color_attachments,
603 depth_stencil_attachment,
604 multiview_mask: None,
605 timestamp_writes: None,
606 occlusion_query_set: None,
607 })
608 .map_err(|e| dst_texture.device.handle_hal_error(e))?;
609 encoder.end_render_pass();
610 }
611 }
612 }
613
614 Ok(())
615}