1 /* This Source Code Form is subject to the terms of the Mozilla Public
2  * License, v. 2.0. If a copy of the MPL was not distributed with this
3  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4 
5 use std::{num::NonZeroU32, ops::Range};
6 
7 #[cfg(feature = "trace")]
8 use crate::device::trace::Command as TraceCommand;
9 use crate::{
10     command::CommandBuffer,
11     conv,
12     device::all_buffer_stages,
13     hub::{GfxBackend, Global, GlobalIdentityHandlerFactory, Token},
14     id::{BufferId, CommandEncoderId, TextureId},
15     memory_init_tracker::{MemoryInitKind, MemoryInitTrackerAction},
16     resource::{BufferUse, TextureUse},
17     track::TextureSelector,
18 };
19 
20 use hal::command::CommandBuffer as _;
21 use thiserror::Error;
22 use wgt::{
23     BufferAddress, BufferSize, BufferUsage, ImageSubresourceRange, TextureAspect, TextureUsage,
24 };
25 
26 /// Error encountered while attempting a clear.
27 #[derive(Clone, Debug, Error)]
28 pub enum ClearError {
29     #[error("to use clear_buffer/texture the CLEAR_COMMANDS feature needs to be enabled")]
30     MissingClearCommandsFeature,
31     #[error("command encoder {0:?} is invalid")]
32     InvalidCommandEncoder(CommandEncoderId),
33     #[error("buffer {0:?} is invalid or destroyed")]
34     InvalidBuffer(BufferId),
35     #[error("texture {0:?} is invalid or destroyed")]
36     InvalidTexture(TextureId),
37     #[error("buffer clear size {0:?} is not a multiple of `COPY_BUFFER_ALIGNMENT`")]
38     UnalignedFillSize(BufferSize),
39     #[error("buffer offset {0:?} is not a multiple of `COPY_BUFFER_ALIGNMENT`")]
40     UnalignedBufferOffset(BufferAddress),
41     #[error("clear of {start_offset}..{end_offset} would end up overrunning the bounds of the buffer of size {buffer_size}")]
42     BufferOverrun {
43         start_offset: BufferAddress,
44         end_offset: BufferAddress,
45         buffer_size: BufferAddress,
46     },
47     #[error("destination buffer/texture is missing the `COPY_DST` usage flag")]
48     MissingCopyDstUsageFlag(Option<BufferId>, Option<TextureId>),
49     #[error("texture lacks the aspects that were specified in the image subresource range. Texture has {texture_aspects:?}, specified was {subresource_range_aspects:?}")]
50     MissingTextureAspect {
51         texture_aspects: hal::format::Aspects,
52         subresource_range_aspects: TextureAspect,
53     },
54     #[error("image subresource level range is outside of the texture's level range. texture range is {texture_level_range:?},  \
55 whereas subesource range specified start {subresource_base_mip_level} and count {subresource_mip_level_count:?}")]
56     InvalidTextureLevelRange {
57         texture_level_range: Range<hal::image::Level>,
58         subresource_base_mip_level: u32,
59         subresource_mip_level_count: Option<NonZeroU32>,
60     },
61     #[error("image subresource layer range is outside of the texture's layer range. texture range is {texture_layer_range:?},  \
62 whereas subesource range specified start {subresource_base_array_layer} and count {subresource_array_layer_count:?}")]
63     InvalidTextureLayerRange {
64         texture_layer_range: Range<hal::image::Layer>,
65         subresource_base_array_layer: u32,
66         subresource_array_layer_count: Option<NonZeroU32>,
67     },
68 }
69 
70 impl<G: GlobalIdentityHandlerFactory> Global<G> {
command_encoder_clear_buffer<B: GfxBackend>( &self, command_encoder_id: CommandEncoderId, dst: BufferId, offset: BufferAddress, size: Option<BufferSize>, ) -> Result<(), ClearError>71     pub fn command_encoder_clear_buffer<B: GfxBackend>(
72         &self,
73         command_encoder_id: CommandEncoderId,
74         dst: BufferId,
75         offset: BufferAddress,
76         size: Option<BufferSize>,
77     ) -> Result<(), ClearError> {
78         profiling::scope!("CommandEncoder::fill_buffer");
79 
80         let hub = B::hub(self);
81         let mut token = Token::root();
82         let (mut cmd_buf_guard, mut token) = hub.command_buffers.write(&mut token);
83         let cmd_buf = CommandBuffer::get_encoder_mut(&mut *cmd_buf_guard, command_encoder_id)
84             .map_err(|_| ClearError::InvalidCommandEncoder(command_encoder_id))?;
85         let (buffer_guard, _) = hub.buffers.read(&mut token);
86 
87         #[cfg(feature = "trace")]
88         if let Some(ref mut list) = cmd_buf.commands {
89             list.push(TraceCommand::ClearBuffer { dst, offset, size });
90         }
91 
92         if !cmd_buf.support_fill_buffer_texture {
93             return Err(ClearError::MissingClearCommandsFeature);
94         }
95 
96         let (dst_buffer, dst_pending) = cmd_buf
97             .trackers
98             .buffers
99             .use_replace(&*buffer_guard, dst, (), BufferUse::COPY_DST)
100             .map_err(ClearError::InvalidBuffer)?;
101         let &(ref dst_raw, _) = dst_buffer
102             .raw
103             .as_ref()
104             .ok_or(ClearError::InvalidBuffer(dst))?;
105         if !dst_buffer.usage.contains(BufferUsage::COPY_DST) {
106             return Err(ClearError::MissingCopyDstUsageFlag(Some(dst), None));
107         }
108 
109         // Check if offset & size are valid.
110         if offset % wgt::COPY_BUFFER_ALIGNMENT != 0 {
111             return Err(ClearError::UnalignedBufferOffset(offset));
112         }
113         if let Some(size) = size {
114             if size.get() % wgt::COPY_BUFFER_ALIGNMENT != 0 {
115                 return Err(ClearError::UnalignedFillSize(size));
116             }
117             let destination_end_offset = offset + size.get();
118             if destination_end_offset > dst_buffer.size {
119                 return Err(ClearError::BufferOverrun {
120                     start_offset: offset,
121                     end_offset: destination_end_offset,
122                     buffer_size: dst_buffer.size,
123                 });
124             }
125         }
126 
127         let num_bytes_filled = size.map_or(dst_buffer.size - offset, |s| s.get());
128         if num_bytes_filled == 0 {
129             log::trace!("Ignoring fill_buffer of size 0");
130             return Ok(());
131         }
132 
133         // Mark dest as initialized.
134         cmd_buf.buffer_memory_init_actions.extend(
135             dst_buffer
136                 .initialization_status
137                 .check(offset..(offset + num_bytes_filled))
138                 .map(|range| MemoryInitTrackerAction {
139                     id: dst,
140                     range,
141                     kind: MemoryInitKind::ImplicitlyInitialized,
142                 }),
143         );
144 
145         // actual hal barrier & operation
146         let dst_barrier = dst_pending
147             .map(|pending| pending.into_hal(dst_buffer))
148             .next();
149         let cmd_buf_raw = cmd_buf.raw.last_mut().unwrap();
150         unsafe {
151             cmd_buf_raw.pipeline_barrier(
152                 all_buffer_stages()..hal::pso::PipelineStage::TRANSFER,
153                 hal::memory::Dependencies::empty(),
154                 dst_barrier.into_iter(),
155             );
156             cmd_buf_raw.fill_buffer(
157                 dst_raw,
158                 hal::buffer::SubRange {
159                     offset,
160                     size: size.map(|s| s.get()),
161                 },
162                 0,
163             );
164         }
165         Ok(())
166     }
167 
command_encoder_clear_image<B: GfxBackend>( &self, command_encoder_id: CommandEncoderId, dst: TextureId, subresource_range: ImageSubresourceRange, ) -> Result<(), ClearError>168     pub fn command_encoder_clear_image<B: GfxBackend>(
169         &self,
170         command_encoder_id: CommandEncoderId,
171         dst: TextureId,
172         subresource_range: ImageSubresourceRange,
173     ) -> Result<(), ClearError> {
174         profiling::scope!("CommandEncoder::clear_image");
175 
176         let hub = B::hub(self);
177         let mut token = Token::root();
178         let (mut cmd_buf_guard, mut token) = hub.command_buffers.write(&mut token);
179         let cmd_buf = CommandBuffer::get_encoder_mut(&mut *cmd_buf_guard, command_encoder_id)
180             .map_err(|_| ClearError::InvalidCommandEncoder(command_encoder_id))?;
181         let (_, mut token) = hub.buffers.read(&mut token); // skip token
182         let (texture_guard, _) = hub.textures.read(&mut token);
183 
184         #[cfg(feature = "trace")]
185         if let Some(ref mut list) = cmd_buf.commands {
186             list.push(TraceCommand::ClearImage {
187                 dst,
188                 subresource_range: subresource_range.clone(),
189             });
190         }
191 
192         if !cmd_buf.support_fill_buffer_texture {
193             return Err(ClearError::MissingClearCommandsFeature);
194         }
195 
196         let dst_texture = texture_guard
197             .get(dst)
198             .map_err(|_| ClearError::InvalidTexture(dst))?;
199 
200         // Check if subresource aspects are valid.
201         let aspects = match subresource_range.aspect {
202             wgt::TextureAspect::All => dst_texture.aspects,
203             wgt::TextureAspect::DepthOnly => hal::format::Aspects::DEPTH,
204             wgt::TextureAspect::StencilOnly => hal::format::Aspects::STENCIL,
205         };
206         if !dst_texture.aspects.contains(aspects) {
207             return Err(ClearError::MissingTextureAspect {
208                 texture_aspects: dst_texture.aspects,
209                 subresource_range_aspects: subresource_range.aspect,
210             });
211         };
212         // Check if subresource level range is valid
213         let subresource_level_end = if let Some(count) = subresource_range.mip_level_count {
214             (subresource_range.base_mip_level + count.get()) as u8
215         } else {
216             dst_texture.full_range.levels.end
217         };
218         if dst_texture.full_range.levels.start > subresource_range.base_mip_level as u8
219             || dst_texture.full_range.levels.end < subresource_level_end
220         {
221             return Err(ClearError::InvalidTextureLevelRange {
222                 texture_level_range: dst_texture.full_range.levels.clone(),
223                 subresource_base_mip_level: subresource_range.base_mip_level,
224                 subresource_mip_level_count: subresource_range.mip_level_count,
225             });
226         }
227         // Check if subresource layer range is valid
228         let subresource_layer_end = if let Some(count) = subresource_range.array_layer_count {
229             (subresource_range.base_array_layer + count.get()) as u16
230         } else {
231             dst_texture.full_range.layers.end
232         };
233         if dst_texture.full_range.layers.start > subresource_range.base_array_layer as u16
234             || dst_texture.full_range.layers.end < subresource_layer_end
235         {
236             return Err(ClearError::InvalidTextureLayerRange {
237                 texture_layer_range: dst_texture.full_range.layers.clone(),
238                 subresource_base_array_layer: subresource_range.base_array_layer,
239                 subresource_array_layer_count: subresource_range.array_layer_count,
240             });
241         }
242 
243         // query from tracker with usage (and check usage)
244         let (dst_texture, dst_pending) = cmd_buf
245             .trackers
246             .textures
247             .use_replace(
248                 &*texture_guard,
249                 dst,
250                 TextureSelector {
251                     levels: subresource_range.base_mip_level as u8..subresource_level_end,
252                     layers: subresource_range.base_array_layer as u16..subresource_layer_end,
253                 },
254                 TextureUse::COPY_DST,
255             )
256             .map_err(ClearError::InvalidTexture)?;
257         let &(ref dst_raw, _) = dst_texture
258             .raw
259             .as_ref()
260             .ok_or(ClearError::InvalidTexture(dst))?;
261         if !dst_texture.usage.contains(TextureUsage::COPY_DST) {
262             return Err(ClearError::MissingCopyDstUsageFlag(None, Some(dst)));
263         }
264 
265         // actual hal barrier & operation
266         let dst_barrier = dst_pending
267             .map(|pending| pending.into_hal(dst_texture))
268             .next();
269         let cmd_buf_raw = cmd_buf.raw.last_mut().unwrap();
270         unsafe {
271             cmd_buf_raw.pipeline_barrier(
272                 all_buffer_stages()..hal::pso::PipelineStage::TRANSFER,
273                 hal::memory::Dependencies::empty(),
274                 dst_barrier.into_iter(),
275             );
276             cmd_buf_raw.clear_image(
277                 dst_raw,
278                 hal::image::Layout::TransferDstOptimal,
279                 hal::command::ClearValue {
280                     color: hal::command::ClearColor {
281                         float32: conv::map_color_f32(&wgt::Color::TRANSPARENT),
282                     },
283                 },
284                 std::iter::once(hal::image::SubresourceRange {
285                     aspects,
286                     level_start: subresource_range.base_mip_level as u8,
287                     level_count: subresource_range.mip_level_count.map(|c| c.get() as u8),
288                     layer_start: subresource_range.base_array_layer as u16,
289                     layer_count: subresource_range.array_layer_count.map(|c| c.get() as u16),
290                 }),
291             );
292         }
293         Ok(())
294     }
295 }
296