li_wgpu_core/command/
clear.rs

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