Skip to main content

wgpu_core/command/
clear.rs

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/// Error encountered while attempting a clear.
27#[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    // Check if offset & size are valid.
194    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    // This must happen after parameter validation (so that errors are reported
218    // as required by the spec), but before any side effects.
219    if offset == end_offset {
220        log::trace!("Ignoring fill_buffer of size 0");
221        return Ok(());
222    }
223
224    // Mark dest as initialized.
225    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    // actual hal barrier & operation
234    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
243/// Validate and encode a "Clear Texture" command.
244///
245/// This function implements `CommandEncoder::clear_texture` when invoked via
246/// the command encoder APIs or trace playback. It has the suffix `_cmd` to
247/// distinguish it from [`clear_texture`]. [`clear_texture`], used internally by
248/// this function, is a lower-level function that encodes a texture clear
249/// operation without validating it.
250pub(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    // Check that the clear targets all aspects, and no invalid aspects.
261    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    // Check if subresource level range is valid
277    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    // Check if subresource layer range is valid
288    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
317/// Encode a texture clear operation.
318///
319/// This function encodes a texture clear operation without validating it.
320/// Texture clears requested via the API call this function via
321/// [`clear_texture_cmd`], which does the validation. This function is also
322/// called directly from various places within wgpu that need to clear a
323/// texture.
324///
325/// For a 3D texture:
326/// - If `depth_slice` is `Some`, clear only that depth slice (in this case,
327///   `range.mip_range` must be a single mip level).
328/// - If `depth_slice` is `None`, clear entire mip level(s).
329pub(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    // Issue the right barrier.
343    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    // If we're in a texture-init usecase, we know that the texture is already
364    // tracked since whatever caused the init requirement, will have caused the
365    // usage tracker to be aware of the texture. Meaning, that it is safe to
366    // call call change_replace_tracked if the life_guard is already gone (i.e.
367    // the user no longer holds on to this texture).
368    //
369    // On the other hand, when coming via command_encoder_clear_texture, the
370    // life_guard is still there since in order to call it a texture object is
371    // needed.
372    //
373    // We could in theory distinguish these two scenarios in the internal
374    // clear_texture api in order to remove this check and call the cheaper
375    // change_replace_tracked whenever possible.
376    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    // Record actual clearing
385    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, // Buffer of size device::ZERO_BUFFER_SIZE
417    range: TextureInitRange,
418    depth_slice: Option<u32>,
419    encoder: &mut dyn hal::DynCommandEncoder,
420    dst_raw: &dyn hal::DynTexture,
421) {
422    // These preconditions should apply to `clear_texture` as a whole, but since they're
423    // currently only serving this function, seems clearer to keep them here.
424    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        // Gather list of zero_buffer copies and issue a single command then to perform them
451        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            // Subsample for multi-planar formats (e.g. NV12/P010 chroma plane is half-res).
460            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            // Round to multiple of block size
463            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            // round down to a multiple of rows needed by the texture format
473            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                // TODO: Only doing one layer at a time for volume textures right now.
492                for z in z_range.clone() {
493                    // May need multiple copies for each subresource! However, we
494                    // assume that we never need to split a row.
495                    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, // Always full rows
510                                    y: mip_size.height - num_rows_left,
511                                    z,
512                                },
513                                aspect: hal_aspect,
514                            },
515                            size: hal::CopyExtent {
516                                width: mip_size.width, // full row
517                                height: num_rows,
518                                depth: 1, // Only single slice of volume texture at a time right now
519                            },
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, // Only one layer is cleared at a time.
547    };
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}