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