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,
9    global::Global,
10    hal_label,
11    id::{BufferId, CommandEncoderId, TextureId},
12    init_tracker::{MemoryInitKind, TextureInitRange},
13    resource::{
14        Buffer, DestroyedResourceError, InvalidOrDestroyedResourceError, InvalidResourceError,
15        Labeled, MissingBufferUsageError, ParentDevice, RawResourceAccess, ResourceErrorIdent,
16        Texture, TextureClearMode,
17    },
18    snatch::SnatchGuard,
19    track::TextureTrackerSetSingle,
20};
21
22use thiserror::Error;
23use wgt::{
24    error::{ErrorType, WebGpuError},
25    math::align_to,
26    BufferAddress, BufferUsages, ImageSubresourceRange, TextureAspect, TextureSelector,
27};
28
29/// Error encountered while attempting a clear.
30#[derive(Clone, Debug, Error)]
31#[non_exhaustive]
32pub enum ClearError {
33    #[error(transparent)]
34    DestroyedResource(#[from] DestroyedResourceError),
35    #[error(transparent)]
36    MissingFeatures(#[from] MissingFeatures),
37    #[error("{0} can not be cleared")]
38    NoValidTextureClearMode(ResourceErrorIdent),
39    #[error("Buffer clear size {0:?} is not a multiple of `COPY_BUFFER_ALIGNMENT`")]
40    UnalignedFillSize(BufferAddress),
41    #[error("Buffer offset {0:?} is not a multiple of `COPY_BUFFER_ALIGNMENT`")]
42    UnalignedBufferOffset(BufferAddress),
43    #[error("Clear starts at offset {start_offset} with size of {requested_size}, but these added together exceed `u64::MAX`")]
44    OffsetPlusSizeExceeds64BitBounds {
45        start_offset: BufferAddress,
46        requested_size: BufferAddress,
47    },
48    #[error("Clear of {start_offset}..{end_offset} would end up overrunning the bounds of the buffer of size {buffer_size}")]
49    BufferOverrun {
50        start_offset: BufferAddress,
51        end_offset: BufferAddress,
52        buffer_size: BufferAddress,
53    },
54    #[error(transparent)]
55    MissingBufferUsage(#[from] MissingBufferUsageError),
56    #[error("Texture lacks the aspects that were specified in the image subresource range. Texture with format {texture_format:?}, specified was {subresource_range_aspects:?}")]
57    MissingTextureAspect {
58        texture_format: wgt::TextureFormat,
59        subresource_range_aspects: TextureAspect,
60    },
61    #[error("Image subresource level range is outside of the texture's level range. texture range is {texture_level_range:?},  \
62whereas subesource range specified start {subresource_base_mip_level} and count {subresource_mip_level_count:?}")]
63    InvalidTextureLevelRange {
64        texture_level_range: Range<u32>,
65        subresource_base_mip_level: u32,
66        subresource_mip_level_count: Option<u32>,
67    },
68    #[error("Image subresource layer range is outside of the texture's layer range. texture range is {texture_layer_range:?},  \
69whereas subesource range specified start {subresource_base_array_layer} and count {subresource_array_layer_count:?}")]
70    InvalidTextureLayerRange {
71        texture_layer_range: Range<u32>,
72        subresource_base_array_layer: u32,
73        subresource_array_layer_count: Option<u32>,
74    },
75    #[error(transparent)]
76    Device(#[from] DeviceError),
77    #[error(transparent)]
78    EncoderState(#[from] EncoderStateError),
79    #[error(transparent)]
80    InvalidResource(#[from] InvalidResourceError),
81}
82
83impl From<InvalidOrDestroyedResourceError> for ClearError {
84    fn from(value: InvalidOrDestroyedResourceError) -> Self {
85        match value {
86            InvalidOrDestroyedResourceError::InvalidResource(e) => Self::InvalidResource(e),
87            InvalidOrDestroyedResourceError::DestroyedResource(e) => Self::DestroyedResource(e),
88        }
89    }
90}
91
92impl WebGpuError for ClearError {
93    fn webgpu_error_type(&self) -> ErrorType {
94        match self {
95            Self::DestroyedResource(e) => e.webgpu_error_type(),
96            Self::MissingFeatures(e) => e.webgpu_error_type(),
97            Self::MissingBufferUsage(e) => e.webgpu_error_type(),
98            Self::Device(e) => e.webgpu_error_type(),
99            Self::EncoderState(e) => e.webgpu_error_type(),
100            Self::InvalidResource(e) => e.webgpu_error_type(),
101            Self::NoValidTextureClearMode(..)
102            | Self::UnalignedFillSize(..)
103            | Self::UnalignedBufferOffset(..)
104            | Self::OffsetPlusSizeExceeds64BitBounds { .. }
105            | Self::BufferOverrun { .. }
106            | Self::MissingTextureAspect { .. }
107            | Self::InvalidTextureLevelRange { .. }
108            | Self::InvalidTextureLayerRange { .. } => ErrorType::Validation,
109        }
110    }
111}
112
113impl super::CommandEncoder {
114    pub fn clear_buffer(
115        self: &Arc<Self>,
116        dst: Arc<Buffer>,
117        offset: BufferAddress,
118        size: Option<BufferAddress>,
119    ) -> Result<(), EncoderStateError> {
120        profiling::scope!("CommandEncoder::clear_buffer");
121        api_log!("CommandEncoder::clear_buffer {:?}", Arc::as_ptr(&dst));
122
123        let mut cmd_buf_data = self.data.lock();
124
125        cmd_buf_data.push_with(|| -> Result<_, ClearError> {
126            dst.check_is_valid()?;
127            Ok(ArcCommand::ClearBuffer { dst, offset, size })
128        })
129    }
130
131    pub fn clear_texture(
132        self: &Arc<Self>,
133        dst: Arc<Texture>,
134        subresource_range: &ImageSubresourceRange,
135    ) -> Result<(), EncoderStateError> {
136        profiling::scope!("CommandEncoder::clear_texture");
137        api_log!("CommandEncoder::clear_texture {:?}", Arc::as_ptr(&dst));
138
139        let mut cmd_buf_data = self.data.lock();
140
141        cmd_buf_data.push_with(|| -> Result<_, ClearError> {
142            dst.check_valid()?;
143            Ok(ArcCommand::ClearTexture {
144                dst,
145                subresource_range: *subresource_range,
146            })
147        })
148    }
149}
150
151impl Global {
152    pub fn command_encoder_clear_buffer(
153        &self,
154        command_encoder_id: CommandEncoderId,
155        dst: BufferId,
156        offset: BufferAddress,
157        size: Option<BufferAddress>,
158    ) -> Result<(), EncoderStateError> {
159        let hub = &self.hub;
160
161        let cmd_enc = hub.command_encoders.get(command_encoder_id);
162        cmd_enc.clear_buffer(hub.buffers.get(dst), offset, size)
163    }
164
165    pub fn command_encoder_clear_texture(
166        &self,
167        command_encoder_id: CommandEncoderId,
168        dst: TextureId,
169        subresource_range: &ImageSubresourceRange,
170    ) -> Result<(), EncoderStateError> {
171        let hub = &self.hub;
172
173        let cmd_enc = hub.command_encoders.get(command_encoder_id);
174
175        cmd_enc.clear_texture(hub.textures.get(dst), subresource_range)
176    }
177}
178
179pub(super) fn clear_buffer(
180    state: &mut EncodingState,
181    dst_buffer: Arc<Buffer>,
182    offset: BufferAddress,
183    size: Option<BufferAddress>,
184) -> Result<(), ClearError> {
185    dst_buffer.same_device(state.device)?;
186
187    let dst_pending = state
188        .tracker
189        .buffers
190        .set_single(&dst_buffer, wgt::BufferUses::COPY_DST);
191
192    let dst_raw = dst_buffer.try_raw(state.snatch_guard)?;
193    dst_buffer.check_usage(BufferUsages::COPY_DST)?;
194
195    // Check if offset & size are valid.
196    if !offset.is_multiple_of(wgt::COPY_BUFFER_ALIGNMENT) {
197        return Err(ClearError::UnalignedBufferOffset(offset));
198    }
199
200    let size = size.unwrap_or(dst_buffer.size.saturating_sub(offset));
201    if !size.is_multiple_of(wgt::COPY_BUFFER_ALIGNMENT) {
202        return Err(ClearError::UnalignedFillSize(size));
203    }
204    let end_offset =
205        offset
206            .checked_add(size)
207            .ok_or(ClearError::OffsetPlusSizeExceeds64BitBounds {
208                start_offset: offset,
209                requested_size: size,
210            })?;
211    if end_offset > dst_buffer.size {
212        return Err(ClearError::BufferOverrun {
213            start_offset: offset,
214            end_offset,
215            buffer_size: dst_buffer.size,
216        });
217    }
218
219    // This must happen after parameter validation (so that errors are reported
220    // as required by the spec), but before any side effects.
221    if offset == end_offset {
222        log::trace!("Ignoring fill_buffer of size 0");
223        return Ok(());
224    }
225
226    // Mark dest as initialized.
227    state
228        .buffer_memory_init_actions
229        .extend(dst_buffer.initialization_status.read().create_action(
230            &dst_buffer,
231            offset..end_offset,
232            MemoryInitKind::ImplicitlyInitialized,
233        ));
234
235    // actual hal barrier & operation
236    let dst_barrier = dst_pending.map(|pending| pending.into_hal(&dst_buffer, state.snatch_guard));
237    unsafe {
238        state.raw_encoder.transition_buffers(dst_barrier.as_slice());
239        state.raw_encoder.clear_buffer(dst_raw, offset..end_offset);
240    }
241
242    Ok(())
243}
244
245/// Validate and encode a "Clear Texture" command.
246///
247/// This function implements `CommandEncoder::clear_texture` when invoked via
248/// the command encoder APIs or trace playback. It has the suffix `_cmd` to
249/// distinguish it from [`clear_texture`]. [`clear_texture`], used internally by
250/// this function, is a lower-level function that encodes a texture clear
251/// operation without validating it.
252pub(super) fn clear_texture_cmd(
253    state: &mut EncodingState,
254    dst_texture: Arc<Texture>,
255    subresource_range: &ImageSubresourceRange,
256) -> Result<(), ClearError> {
257    dst_texture.same_device(state.device)?;
258    state
259        .device
260        .require_features(wgt::Features::CLEAR_TEXTURE)?;
261
262    // Check if subresource aspects are valid.
263    let clear_aspects = hal::FormatAspects::new(dst_texture.desc.format, subresource_range.aspect);
264    if clear_aspects.is_empty() {
265        return Err(ClearError::MissingTextureAspect {
266            texture_format: dst_texture.desc.format,
267            subresource_range_aspects: subresource_range.aspect,
268        });
269    };
270
271    // Check if subresource level range is valid
272    let subresource_mip_range = subresource_range.mip_range(dst_texture.full_range.mips.end);
273    if dst_texture.full_range.mips.start > subresource_mip_range.start
274        || dst_texture.full_range.mips.end < subresource_mip_range.end
275    {
276        return Err(ClearError::InvalidTextureLevelRange {
277            texture_level_range: dst_texture.full_range.mips.clone(),
278            subresource_base_mip_level: subresource_range.base_mip_level,
279            subresource_mip_level_count: subresource_range.mip_level_count,
280        });
281    }
282    // Check if subresource layer range is valid
283    let subresource_layer_range = subresource_range.layer_range(dst_texture.full_range.layers.end);
284    if dst_texture.full_range.layers.start > subresource_layer_range.start
285        || dst_texture.full_range.layers.end < subresource_layer_range.end
286    {
287        return Err(ClearError::InvalidTextureLayerRange {
288            texture_layer_range: dst_texture.full_range.layers.clone(),
289            subresource_base_array_layer: subresource_range.base_array_layer,
290            subresource_array_layer_count: subresource_range.array_layer_count,
291        });
292    }
293
294    clear_texture(
295        &dst_texture,
296        TextureInitRange {
297            mip_range: subresource_mip_range,
298            layer_range: subresource_layer_range,
299        },
300        state.raw_encoder,
301        &mut state.tracker.textures,
302        &state.device.alignments,
303        state.device.zero_buffer.as_ref(),
304        state.snatch_guard,
305        state.device.instance_flags,
306    )?;
307
308    Ok(())
309}
310
311/// Encode a texture clear operation.
312///
313/// This function encodes a texture clear operation without validating it.
314/// Texture clears requested via the API call this function via
315/// [`clear_texture_cmd`], which does the validation. This function is also
316/// called directly from various places within wgpu that need to clear a
317/// texture.
318pub(crate) fn clear_texture<T: TextureTrackerSetSingle>(
319    dst_texture: &Arc<Texture>,
320    range: TextureInitRange,
321    encoder: &mut dyn hal::DynCommandEncoder,
322    texture_tracker: &mut T,
323    alignments: &hal::Alignments,
324    zero_buffer: &dyn hal::DynBuffer,
325    snatch_guard: &SnatchGuard<'_>,
326    instance_flags: wgt::InstanceFlags,
327) -> Result<(), ClearError> {
328    let dst_raw = dst_texture.try_inner(snatch_guard)?.raw();
329
330    // Issue the right barrier.
331    let clear_usage = match *dst_texture.clear_mode.read() {
332        TextureClearMode::BufferCopy => wgt::TextureUses::COPY_DST,
333        TextureClearMode::RenderPass {
334            is_color: false, ..
335        } => wgt::TextureUses::DEPTH_STENCIL_WRITE,
336        TextureClearMode::Surface { .. } | TextureClearMode::RenderPass { is_color: true, .. } => {
337            wgt::TextureUses::COLOR_TARGET
338        }
339        TextureClearMode::None => {
340            return Err(ClearError::NoValidTextureClearMode(
341                dst_texture.error_ident(),
342            ));
343        }
344    };
345
346    let selector = TextureSelector {
347        mips: range.mip_range.clone(),
348        layers: range.layer_range.clone(),
349    };
350
351    // If we're in a texture-init usecase, we know that the texture is already
352    // tracked since whatever caused the init requirement, will have caused the
353    // usage tracker to be aware of the texture. Meaning, that it is safe to
354    // call call change_replace_tracked if the life_guard is already gone (i.e.
355    // the user no longer holds on to this texture).
356    //
357    // On the other hand, when coming via command_encoder_clear_texture, the
358    // life_guard is still there since in order to call it a texture object is
359    // needed.
360    //
361    // We could in theory distinguish these two scenarios in the internal
362    // clear_texture api in order to remove this check and call the cheaper
363    // change_replace_tracked whenever possible.
364    let dst_barrier = texture_tracker
365        .set_single(dst_texture, selector, clear_usage)
366        .map(|pending| pending.into_hal(dst_raw))
367        .collect::<Vec<_>>();
368    unsafe {
369        encoder.transition_textures(&dst_barrier);
370    }
371
372    // Record actual clearing
373    let clear_mode = dst_texture.clear_mode.read();
374    match *clear_mode {
375        TextureClearMode::BufferCopy => clear_texture_via_buffer_copies(
376            &dst_texture.desc,
377            alignments,
378            zero_buffer,
379            range,
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    encoder: &mut dyn hal::DynCommandEncoder,
406    dst_raw: &dyn hal::DynTexture,
407) {
408    assert!(!texture_desc.format.is_depth_stencil_format());
409
410    if texture_desc.format == wgt::TextureFormat::NV12
411        || texture_desc.format == wgt::TextureFormat::P010
412    {
413        // TODO: Currently COPY_DST for NV12 and P010 textures is unsupported.
414        return;
415    }
416
417    // Gather list of zero_buffer copies and issue a single command then to perform them
418    let mut zero_buffer_copy_regions = Vec::new();
419    let buffer_copy_pitch = alignments.buffer_copy_pitch.get() as u32;
420    let (block_width, block_height) = texture_desc.format.block_dimensions();
421    let block_size = texture_desc.format.block_copy_size(None).unwrap();
422
423    let bytes_per_row_alignment = get_lowest_common_denom(buffer_copy_pitch, block_size);
424
425    for mip_level in range.mip_range {
426        let mut mip_size = texture_desc.mip_level_size(mip_level).unwrap();
427        // Round to multiple of block size
428        mip_size.width = align_to(mip_size.width, block_width);
429        mip_size.height = align_to(mip_size.height, block_height);
430
431        let bytes_per_row = align_to(
432            mip_size.width / block_width * block_size,
433            bytes_per_row_alignment,
434        );
435
436        let max_rows_per_copy = crate::device::ZERO_BUFFER_SIZE as u32 / bytes_per_row;
437        // round down to a multiple of rows needed by the texture format
438        let max_rows_per_copy = max_rows_per_copy / block_height * block_height;
439        assert!(
440            max_rows_per_copy > 0,
441            "Zero buffer size is too small to fill a single row \
442            of a texture with format {:?} and desc {:?}",
443            texture_desc.format,
444            texture_desc.size
445        );
446
447        let z_range = 0..(if texture_desc.dimension == wgt::TextureDimension::D3 {
448            mip_size.depth_or_array_layers
449        } else {
450            1
451        });
452
453        for array_layer in range.layer_range.clone() {
454            // TODO: Only doing one layer at a time for volume textures right now.
455            for z in z_range.clone() {
456                // May need multiple copies for each subresource! However, we
457                // assume that we never need to split a row.
458                let mut num_rows_left = mip_size.height;
459                while num_rows_left > 0 {
460                    let num_rows = num_rows_left.min(max_rows_per_copy);
461
462                    zero_buffer_copy_regions.push(hal::BufferTextureCopy {
463                        buffer_layout: wgt::TexelCopyBufferLayout {
464                            offset: 0,
465                            bytes_per_row: Some(bytes_per_row),
466                            rows_per_image: None,
467                        },
468                        texture_base: hal::TextureCopyBase {
469                            mip_level,
470                            array_layer,
471                            origin: wgt::Origin3d {
472                                x: 0, // Always full rows
473                                y: mip_size.height - num_rows_left,
474                                z,
475                            },
476                            aspect: hal::FormatAspects::COLOR,
477                        },
478                        size: hal::CopyExtent {
479                            width: mip_size.width, // full row
480                            height: num_rows,
481                            depth: 1, // Only single slice of volume texture at a time right now
482                        },
483                    });
484
485                    num_rows_left -= num_rows;
486                }
487            }
488        }
489    }
490
491    unsafe {
492        encoder.copy_buffer_to_texture(zero_buffer, dst_raw, &zero_buffer_copy_regions);
493    }
494}
495
496fn clear_texture_via_render_passes(
497    dst_texture: &Texture,
498    range: TextureInitRange,
499    is_color: bool,
500    encoder: &mut dyn hal::DynCommandEncoder,
501    instance_flags: wgt::InstanceFlags,
502) -> Result<(), ClearError> {
503    assert_eq!(dst_texture.desc.dimension, wgt::TextureDimension::D2);
504
505    let extent_base = wgt::Extent3d {
506        width: dst_texture.desc.size.width,
507        height: dst_texture.desc.size.height,
508        depth_or_array_layers: 1, // Only one layer is cleared at a time.
509    };
510
511    let clear_mode = dst_texture.clear_mode.read();
512
513    for mip_level in range.mip_range {
514        let extent = extent_base.mip_level_size(mip_level, dst_texture.desc.dimension);
515        for depth_or_layer in range.layer_range.clone() {
516            let color_attachments_tmp;
517            let (color_attachments, depth_stencil_attachment) = if is_color {
518                color_attachments_tmp = [Some(hal::ColorAttachment {
519                    target: hal::Attachment {
520                        view: Texture::get_clear_view(
521                            &clear_mode,
522                            &dst_texture.desc,
523                            mip_level,
524                            depth_or_layer,
525                        ),
526                        usage: wgt::TextureUses::COLOR_TARGET,
527                    },
528                    depth_slice: None,
529                    resolve_target: None,
530                    ops: hal::AttachmentOps::STORE | hal::AttachmentOps::LOAD_CLEAR,
531                    clear_value: wgt::Color::TRANSPARENT,
532                })];
533                (&color_attachments_tmp[..], None)
534            } else {
535                (
536                    &[][..],
537                    Some(hal::DepthStencilAttachment {
538                        target: hal::Attachment {
539                            view: Texture::get_clear_view(
540                                &clear_mode,
541                                &dst_texture.desc,
542                                mip_level,
543                                depth_or_layer,
544                            ),
545                            usage: wgt::TextureUses::DEPTH_STENCIL_WRITE,
546                        },
547                        depth_ops: hal::AttachmentOps::STORE | hal::AttachmentOps::LOAD_CLEAR,
548                        stencil_ops: hal::AttachmentOps::STORE | hal::AttachmentOps::LOAD_CLEAR,
549                        clear_value: (0.0, 0),
550                    }),
551                )
552            };
553            unsafe {
554                encoder
555                    .begin_render_pass(&hal::RenderPassDescriptor {
556                        label: hal_label(
557                            Some("(wgpu internal) clear_texture clear pass"),
558                            instance_flags,
559                        ),
560                        extent,
561                        sample_count: dst_texture.desc.sample_count,
562                        color_attachments,
563                        depth_stencil_attachment,
564                        multiview_mask: None,
565                        timestamp_writes: None,
566                        occlusion_query_set: None,
567                    })
568                    .map_err(|e| dst_texture.device.handle_hal_error(e))?;
569                encoder.end_render_pass();
570            }
571        }
572    }
573
574    Ok(())
575}