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