1use alloc::{sync::Arc, vec::Vec};
2use core::ops::Range;
3
4#[cfg(feature = "trace")]
5use crate::command::Command as TraceCommand;
6use crate::{
7 api_log,
8 command::{CommandBufferMutable, CommandEncoder, EncoderStateError},
9 device::{DeviceError, MissingFeatures},
10 get_lowest_common_denom,
11 global::Global,
12 hal_label,
13 id::{BufferId, CommandEncoderId, TextureId},
14 init_tracker::{MemoryInitKind, TextureInitRange},
15 resource::{
16 DestroyedResourceError, InvalidResourceError, Labeled, MissingBufferUsageError,
17 ParentDevice, RawResourceAccess, ResourceErrorIdent, Texture, TextureClearMode,
18 },
19 snatch::SnatchGuard,
20 track::TextureTrackerSetSingle,
21};
22
23use thiserror::Error;
24use wgt::{
25 error::{ErrorType, WebGpuError},
26 math::align_to,
27 BufferAddress, BufferUsages, ImageSubresourceRange, TextureAspect, TextureSelector,
28};
29
30#[derive(Clone, Debug, Error)]
32#[non_exhaustive]
33pub enum ClearError {
34 #[error(transparent)]
35 DestroyedResource(#[from] DestroyedResourceError),
36 #[error(transparent)]
37 MissingFeatures(#[from] MissingFeatures),
38 #[error("{0} can not be cleared")]
39 NoValidTextureClearMode(ResourceErrorIdent),
40 #[error("Buffer clear size {0:?} is not a multiple of `COPY_BUFFER_ALIGNMENT`")]
41 UnalignedFillSize(BufferAddress),
42 #[error("Buffer offset {0:?} is not a multiple of `COPY_BUFFER_ALIGNMENT`")]
43 UnalignedBufferOffset(BufferAddress),
44 #[error("Clear starts at offset {start_offset} with size of {requested_size}, but these added together exceed `u64::MAX`")]
45 OffsetPlusSizeExceeds64BitBounds {
46 start_offset: BufferAddress,
47 requested_size: BufferAddress,
48 },
49 #[error("Clear of {start_offset}..{end_offset} would end up overrunning the bounds of the buffer of size {buffer_size}")]
50 BufferOverrun {
51 start_offset: BufferAddress,
52 end_offset: BufferAddress,
53 buffer_size: BufferAddress,
54 },
55 #[error(transparent)]
56 MissingBufferUsage(#[from] MissingBufferUsageError),
57 #[error("Texture lacks the aspects that were specified in the image subresource range. Texture with format {texture_format:?}, specified was {subresource_range_aspects:?}")]
58 MissingTextureAspect {
59 texture_format: wgt::TextureFormat,
60 subresource_range_aspects: TextureAspect,
61 },
62 #[error("Image subresource level range is outside of the texture's level range. texture range is {texture_level_range:?}, \
63whereas subesource range specified start {subresource_base_mip_level} and count {subresource_mip_level_count:?}")]
64 InvalidTextureLevelRange {
65 texture_level_range: Range<u32>,
66 subresource_base_mip_level: u32,
67 subresource_mip_level_count: Option<u32>,
68 },
69 #[error("Image subresource layer range is outside of the texture's layer range. texture range is {texture_layer_range:?}, \
70whereas subesource range specified start {subresource_base_array_layer} and count {subresource_array_layer_count:?}")]
71 InvalidTextureLayerRange {
72 texture_layer_range: Range<u32>,
73 subresource_base_array_layer: u32,
74 subresource_array_layer_count: Option<u32>,
75 },
76 #[error(transparent)]
77 Device(#[from] DeviceError),
78 #[error(transparent)]
79 EncoderState(#[from] EncoderStateError),
80 #[error(transparent)]
81 InvalidResource(#[from] InvalidResourceError),
82}
83
84impl WebGpuError for ClearError {
85 fn webgpu_error_type(&self) -> ErrorType {
86 let e: &dyn WebGpuError = match self {
87 Self::DestroyedResource(e) => e,
88 Self::MissingFeatures(e) => e,
89 Self::MissingBufferUsage(e) => e,
90 Self::Device(e) => e,
91 Self::EncoderState(e) => e,
92 Self::InvalidResource(e) => e,
93 Self::NoValidTextureClearMode(..)
94 | Self::UnalignedFillSize(..)
95 | Self::UnalignedBufferOffset(..)
96 | Self::OffsetPlusSizeExceeds64BitBounds { .. }
97 | Self::BufferOverrun { .. }
98 | Self::MissingTextureAspect { .. }
99 | Self::InvalidTextureLevelRange { .. }
100 | Self::InvalidTextureLayerRange { .. } => return ErrorType::Validation,
101 };
102 e.webgpu_error_type()
103 }
104}
105
106impl Global {
107 pub fn command_encoder_clear_buffer(
108 &self,
109 command_encoder_id: CommandEncoderId,
110 dst: BufferId,
111 offset: BufferAddress,
112 size: Option<BufferAddress>,
113 ) -> Result<(), EncoderStateError> {
114 profiling::scope!("CommandEncoder::clear_buffer");
115 api_log!("CommandEncoder::clear_buffer {dst:?}");
116
117 let hub = &self.hub;
118
119 let cmd_enc = hub.command_encoders.get(command_encoder_id);
120 let mut cmd_buf_data = cmd_enc.data.lock();
121 cmd_buf_data.record_with(|cmd_buf_data| -> Result<(), ClearError> {
122 clear_buffer(cmd_buf_data, hub, &cmd_enc, dst, offset, size)
123 })
124 }
125
126 pub fn command_encoder_clear_texture(
127 &self,
128 command_encoder_id: CommandEncoderId,
129 dst: TextureId,
130 subresource_range: &ImageSubresourceRange,
131 ) -> Result<(), EncoderStateError> {
132 profiling::scope!("CommandEncoder::clear_texture");
133 api_log!("CommandEncoder::clear_texture {dst:?}");
134
135 let hub = &self.hub;
136
137 let cmd_enc = hub.command_encoders.get(command_encoder_id);
138 let mut cmd_buf_data = cmd_enc.data.lock();
139 cmd_buf_data.record_with(|cmd_buf_data| -> Result<(), ClearError> {
140 clear_texture_cmd(cmd_buf_data, hub, &cmd_enc, dst, subresource_range)
141 })
142 }
143}
144
145pub(super) fn clear_buffer(
146 cmd_buf_data: &mut CommandBufferMutable,
147 hub: &crate::hub::Hub,
148 cmd_enc: &Arc<CommandEncoder>,
149 dst: BufferId,
150 offset: BufferAddress,
151 size: Option<BufferAddress>,
152) -> Result<(), ClearError> {
153 #[cfg(feature = "trace")]
154 if let Some(ref mut list) = cmd_buf_data.trace_commands {
155 list.push(TraceCommand::ClearBuffer { dst, offset, size });
156 }
157
158 cmd_enc.device.check_is_valid()?;
159
160 let dst_buffer = hub.buffers.get(dst).get()?;
161
162 dst_buffer.same_device_as(cmd_enc.as_ref())?;
163
164 let dst_pending = cmd_buf_data
165 .trackers
166 .buffers
167 .set_single(&dst_buffer, wgt::BufferUses::COPY_DST);
168
169 let snatch_guard = dst_buffer.device.snatchable_lock.read();
170 let dst_raw = dst_buffer.try_raw(&snatch_guard)?;
171 dst_buffer.check_usage(BufferUsages::COPY_DST)?;
172
173 if offset % wgt::COPY_BUFFER_ALIGNMENT != 0 {
175 return Err(ClearError::UnalignedBufferOffset(offset));
176 }
177
178 let size = size.unwrap_or(dst_buffer.size.saturating_sub(offset));
179 if size % wgt::COPY_BUFFER_ALIGNMENT != 0 {
180 return Err(ClearError::UnalignedFillSize(size));
181 }
182 let end_offset =
183 offset
184 .checked_add(size)
185 .ok_or(ClearError::OffsetPlusSizeExceeds64BitBounds {
186 start_offset: offset,
187 requested_size: size,
188 })?;
189 if end_offset > dst_buffer.size {
190 return Err(ClearError::BufferOverrun {
191 start_offset: offset,
192 end_offset,
193 buffer_size: dst_buffer.size,
194 });
195 }
196
197 if offset == end_offset {
198 log::trace!("Ignoring fill_buffer of size 0");
199 return Ok(());
200 }
201
202 cmd_buf_data.buffer_memory_init_actions.extend(
204 dst_buffer.initialization_status.read().create_action(
205 &dst_buffer,
206 offset..end_offset,
207 MemoryInitKind::ImplicitlyInitialized,
208 ),
209 );
210
211 let dst_barrier = dst_pending.map(|pending| pending.into_hal(&dst_buffer, &snatch_guard));
213 let cmd_buf_raw = cmd_buf_data.encoder.open()?;
214 unsafe {
215 cmd_buf_raw.transition_buffers(dst_barrier.as_slice());
216 cmd_buf_raw.clear_buffer(dst_raw, offset..end_offset);
217 }
218
219 Ok(())
220}
221
222pub(super) fn clear_texture_cmd(
230 cmd_buf_data: &mut CommandBufferMutable,
231 hub: &crate::hub::Hub,
232 cmd_enc: &Arc<CommandEncoder>,
233 dst: TextureId,
234 subresource_range: &ImageSubresourceRange,
235) -> Result<(), ClearError> {
236 #[cfg(feature = "trace")]
237 if let Some(ref mut list) = cmd_buf_data.trace_commands {
238 list.push(TraceCommand::ClearTexture {
239 dst,
240 subresource_range: *subresource_range,
241 });
242 }
243
244 cmd_enc.device.check_is_valid()?;
245
246 cmd_enc
247 .device
248 .require_features(wgt::Features::CLEAR_TEXTURE)?;
249
250 let dst_texture = hub.textures.get(dst).get()?;
251
252 dst_texture.same_device_as(cmd_enc.as_ref())?;
253
254 let clear_aspects = hal::FormatAspects::new(dst_texture.desc.format, subresource_range.aspect);
256 if clear_aspects.is_empty() {
257 return Err(ClearError::MissingTextureAspect {
258 texture_format: dst_texture.desc.format,
259 subresource_range_aspects: subresource_range.aspect,
260 });
261 };
262
263 let subresource_mip_range = subresource_range.mip_range(dst_texture.full_range.mips.end);
265 if dst_texture.full_range.mips.start > subresource_mip_range.start
266 || dst_texture.full_range.mips.end < subresource_mip_range.end
267 {
268 return Err(ClearError::InvalidTextureLevelRange {
269 texture_level_range: dst_texture.full_range.mips.clone(),
270 subresource_base_mip_level: subresource_range.base_mip_level,
271 subresource_mip_level_count: subresource_range.mip_level_count,
272 });
273 }
274 let subresource_layer_range = subresource_range.layer_range(dst_texture.full_range.layers.end);
276 if dst_texture.full_range.layers.start > subresource_layer_range.start
277 || dst_texture.full_range.layers.end < subresource_layer_range.end
278 {
279 return Err(ClearError::InvalidTextureLayerRange {
280 texture_layer_range: dst_texture.full_range.layers.clone(),
281 subresource_base_array_layer: subresource_range.base_array_layer,
282 subresource_array_layer_count: subresource_range.array_layer_count,
283 });
284 }
285
286 let device = &cmd_enc.device;
287 device.check_is_valid()?;
288 let (encoder, tracker) = cmd_buf_data.open_encoder_and_tracker()?;
289
290 let snatch_guard = device.snatchable_lock.read();
291 clear_texture(
292 &dst_texture,
293 TextureInitRange {
294 mip_range: subresource_mip_range,
295 layer_range: subresource_layer_range,
296 },
297 encoder,
298 &mut tracker.textures,
299 &device.alignments,
300 device.zero_buffer.as_ref(),
301 &snatch_guard,
302 device.instance_flags,
303 )?;
304
305 Ok(())
306}
307
308pub(crate) fn clear_texture<T: TextureTrackerSetSingle>(
316 dst_texture: &Arc<Texture>,
317 range: TextureInitRange,
318 encoder: &mut dyn hal::DynCommandEncoder,
319 texture_tracker: &mut T,
320 alignments: &hal::Alignments,
321 zero_buffer: &dyn hal::DynBuffer,
322 snatch_guard: &SnatchGuard<'_>,
323 instance_flags: wgt::InstanceFlags,
324) -> Result<(), ClearError> {
325 let dst_raw = dst_texture.try_raw(snatch_guard)?;
326
327 let clear_usage = match *dst_texture.clear_mode.read() {
329 TextureClearMode::BufferCopy => wgt::TextureUses::COPY_DST,
330 TextureClearMode::RenderPass {
331 is_color: false, ..
332 } => wgt::TextureUses::DEPTH_STENCIL_WRITE,
333 TextureClearMode::Surface { .. } | TextureClearMode::RenderPass { is_color: true, .. } => {
334 wgt::TextureUses::COLOR_TARGET
335 }
336 TextureClearMode::None => {
337 return Err(ClearError::NoValidTextureClearMode(
338 dst_texture.error_ident(),
339 ));
340 }
341 };
342
343 let selector = TextureSelector {
344 mips: range.mip_range.clone(),
345 layers: range.layer_range.clone(),
346 };
347
348 let dst_barrier = texture_tracker
362 .set_single(dst_texture, selector, clear_usage)
363 .map(|pending| pending.into_hal(dst_raw))
364 .collect::<Vec<_>>();
365 unsafe {
366 encoder.transition_textures(&dst_barrier);
367 }
368
369 let clear_mode = dst_texture.clear_mode.read();
371 match *clear_mode {
372 TextureClearMode::BufferCopy => clear_texture_via_buffer_copies(
373 &dst_texture.desc,
374 alignments,
375 zero_buffer,
376 range,
377 encoder,
378 dst_raw,
379 ),
380 TextureClearMode::Surface { .. } => {
381 drop(clear_mode);
382 clear_texture_via_render_passes(dst_texture, range, true, encoder, instance_flags)?
383 }
384 TextureClearMode::RenderPass { is_color, .. } => {
385 drop(clear_mode);
386 clear_texture_via_render_passes(dst_texture, range, is_color, encoder, instance_flags)?
387 }
388 TextureClearMode::None => {
389 return Err(ClearError::NoValidTextureClearMode(
390 dst_texture.error_ident(),
391 ));
392 }
393 }
394 Ok(())
395}
396
397fn clear_texture_via_buffer_copies(
398 texture_desc: &wgt::TextureDescriptor<(), Vec<wgt::TextureFormat>>,
399 alignments: &hal::Alignments,
400 zero_buffer: &dyn hal::DynBuffer, range: TextureInitRange,
402 encoder: &mut dyn hal::DynCommandEncoder,
403 dst_raw: &dyn hal::DynTexture,
404) {
405 assert!(!texture_desc.format.is_depth_stencil_format());
406
407 if texture_desc.format == wgt::TextureFormat::NV12
408 || texture_desc.format == wgt::TextureFormat::P010
409 {
410 return;
412 }
413
414 let mut zero_buffer_copy_regions = Vec::new();
416 let buffer_copy_pitch = alignments.buffer_copy_pitch.get() as u32;
417 let (block_width, block_height) = texture_desc.format.block_dimensions();
418 let block_size = texture_desc.format.block_copy_size(None).unwrap();
419
420 let bytes_per_row_alignment = get_lowest_common_denom(buffer_copy_pitch, block_size);
421
422 for mip_level in range.mip_range {
423 let mut mip_size = texture_desc.mip_level_size(mip_level).unwrap();
424 mip_size.width = align_to(mip_size.width, block_width);
426 mip_size.height = align_to(mip_size.height, block_height);
427
428 let bytes_per_row = align_to(
429 mip_size.width / block_width * block_size,
430 bytes_per_row_alignment,
431 );
432
433 let max_rows_per_copy = crate::device::ZERO_BUFFER_SIZE as u32 / bytes_per_row;
434 let max_rows_per_copy = max_rows_per_copy / block_height * block_height;
436 assert!(
437 max_rows_per_copy > 0,
438 "Zero buffer size is too small to fill a single row \
439 of a texture with format {:?} and desc {:?}",
440 texture_desc.format,
441 texture_desc.size
442 );
443
444 let z_range = 0..(if texture_desc.dimension == wgt::TextureDimension::D3 {
445 mip_size.depth_or_array_layers
446 } else {
447 1
448 });
449
450 for array_layer in range.layer_range.clone() {
451 for z in z_range.clone() {
453 let mut num_rows_left = mip_size.height;
456 while num_rows_left > 0 {
457 let num_rows = num_rows_left.min(max_rows_per_copy);
458
459 zero_buffer_copy_regions.push(hal::BufferTextureCopy {
460 buffer_layout: wgt::TexelCopyBufferLayout {
461 offset: 0,
462 bytes_per_row: Some(bytes_per_row),
463 rows_per_image: None,
464 },
465 texture_base: hal::TextureCopyBase {
466 mip_level,
467 array_layer,
468 origin: wgt::Origin3d {
469 x: 0, y: mip_size.height - num_rows_left,
471 z,
472 },
473 aspect: hal::FormatAspects::COLOR,
474 },
475 size: hal::CopyExtent {
476 width: mip_size.width, height: num_rows,
478 depth: 1, },
480 });
481
482 num_rows_left -= num_rows;
483 }
484 }
485 }
486 }
487
488 unsafe {
489 encoder.copy_buffer_to_texture(zero_buffer, dst_raw, &zero_buffer_copy_regions);
490 }
491}
492
493fn clear_texture_via_render_passes(
494 dst_texture: &Texture,
495 range: TextureInitRange,
496 is_color: bool,
497 encoder: &mut dyn hal::DynCommandEncoder,
498 instance_flags: wgt::InstanceFlags,
499) -> Result<(), ClearError> {
500 assert_eq!(dst_texture.desc.dimension, wgt::TextureDimension::D2);
501
502 let extent_base = wgt::Extent3d {
503 width: dst_texture.desc.size.width,
504 height: dst_texture.desc.size.height,
505 depth_or_array_layers: 1, };
507
508 let clear_mode = dst_texture.clear_mode.read();
509
510 for mip_level in range.mip_range {
511 let extent = extent_base.mip_level_size(mip_level, dst_texture.desc.dimension);
512 for depth_or_layer in range.layer_range.clone() {
513 let color_attachments_tmp;
514 let (color_attachments, depth_stencil_attachment) = if is_color {
515 color_attachments_tmp = [Some(hal::ColorAttachment {
516 target: hal::Attachment {
517 view: Texture::get_clear_view(
518 &clear_mode,
519 &dst_texture.desc,
520 mip_level,
521 depth_or_layer,
522 ),
523 usage: wgt::TextureUses::COLOR_TARGET,
524 },
525 depth_slice: None,
526 resolve_target: None,
527 ops: hal::AttachmentOps::STORE,
528 clear_value: wgt::Color::TRANSPARENT,
529 })];
530 (&color_attachments_tmp[..], None)
531 } else {
532 (
533 &[][..],
534 Some(hal::DepthStencilAttachment {
535 target: hal::Attachment {
536 view: Texture::get_clear_view(
537 &clear_mode,
538 &dst_texture.desc,
539 mip_level,
540 depth_or_layer,
541 ),
542 usage: wgt::TextureUses::DEPTH_STENCIL_WRITE,
543 },
544 depth_ops: hal::AttachmentOps::STORE,
545 stencil_ops: hal::AttachmentOps::STORE,
546 clear_value: (0.0, 0),
547 }),
548 )
549 };
550 unsafe {
551 encoder
552 .begin_render_pass(&hal::RenderPassDescriptor {
553 label: hal_label(
554 Some("(wgpu internal) clear_texture clear pass"),
555 instance_flags,
556 ),
557 extent,
558 sample_count: dst_texture.desc.sample_count,
559 color_attachments,
560 depth_stencil_attachment,
561 multiview: None,
562 timestamp_writes: None,
563 occlusion_query_set: None,
564 })
565 .map_err(|e| dst_texture.device.handle_hal_error(e))?;
566 encoder.end_render_pass();
567 }
568 }
569 }
570
571 Ok(())
572}