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