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