wgpu_core/command/
clear.rs

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