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#[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 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 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 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 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 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 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 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 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 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, 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 return;
361 }
362
363 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 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 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 for z in z_range.clone() {
402 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, 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, height: num_rows,
427 depth: 1, },
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, };
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}