wgpu_core/command/
clear.rs

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