Skip to main content

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