wgpu_core/command/
clear.rs

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