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#[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 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 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 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 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 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 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 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 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 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, 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 return;
380 }
381
382 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 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 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 for z in z_range.clone() {
421 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, 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, height: num_rows,
446 depth: 1, },
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, };
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}