wgpu_core/command/
clear.rs

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