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("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    // Check if offset & size are valid.
188    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    // This must happen after parameter validation (so that errors are reported
212    // as required by the spec), but before any side effects.
213    if offset == end_offset {
214        log::trace!("Ignoring fill_buffer of size 0");
215        return Ok(());
216    }
217
218    // Mark dest as initialized.
219    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    // actual hal barrier & operation
228    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
237/// Validate and encode a "Clear Texture" command.
238///
239/// This function implements `CommandEncoder::clear_texture` when invoked via
240/// the command encoder APIs or trace playback. It has the suffix `_cmd` to
241/// distinguish it from [`clear_texture`]. [`clear_texture`], used internally by
242/// this function, is a lower-level function that encodes a texture clear
243/// operation without validating it.
244pub(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    // Check if subresource aspects are valid.
255    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    // Check if subresource level range is valid
264    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    // Check if subresource layer range is valid
275    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
304/// Encode a texture clear operation.
305///
306/// This function encodes a texture clear operation without validating it.
307/// Texture clears requested via the API call this function via
308/// [`clear_texture_cmd`], which does the validation. This function is also
309/// called directly from various places within wgpu that need to clear a
310/// texture.
311///
312/// For a 3D texture:
313/// - If `depth_slice` is `Some`, clear only that depth slice (in this case,
314///   `range.mip_range` must be a single mip level).
315/// - If `depth_slice` is `None`, clear entire mip level(s).
316pub(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    // Issue the right barrier.
330    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    // If we're in a texture-init usecase, we know that the texture is already
351    // tracked since whatever caused the init requirement, will have caused the
352    // usage tracker to be aware of the texture. Meaning, that it is safe to
353    // call call change_replace_tracked if the life_guard is already gone (i.e.
354    // the user no longer holds on to this texture).
355    //
356    // On the other hand, when coming via command_encoder_clear_texture, the
357    // life_guard is still there since in order to call it a texture object is
358    // needed.
359    //
360    // We could in theory distinguish these two scenarios in the internal
361    // clear_texture api in order to remove this check and call the cheaper
362    // change_replace_tracked whenever possible.
363    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    // Record actual clearing
372    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, // Buffer of size device::ZERO_BUFFER_SIZE
404    range: TextureInitRange,
405    depth_slice: Option<u32>,
406    encoder: &mut dyn hal::DynCommandEncoder,
407    dst_raw: &dyn hal::DynTexture,
408) {
409    // These preconditions should apply to `clear_texture` as a whole, but since they're
410    // currently only serving this function, seems clearer to keep them here.
411    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        // TODO: Currently COPY_DST for NV12 and P010 textures is unsupported.
425        return;
426    }
427
428    // Gather list of zero_buffer copies and issue a single command then to perform them
429    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        // Round to multiple of block size
439        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        // round down to a multiple of rows needed by the texture format
449        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            // TODO: Only doing one layer at a time for volume textures right now.
468            for z in z_range.clone() {
469                // May need multiple copies for each subresource! However, we
470                // assume that we never need to split a row.
471                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, // Always full rows
486                                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, // full row
493                            height: num_rows,
494                            depth: 1, // Only single slice of volume texture at a time right now
495                        },
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, // Only one layer is cleared at a time.
522    };
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}