wgpu_types/
transfers.rs

1use crate::{BufferAddress, Extent3d, TexelCopyBufferLayout, TextureAspect, TextureFormat};
2
3impl TexelCopyBufferLayout {
4    /// Extract a variety of information about the given copy operation.
5    ///
6    /// Returns an error if the size of the copy overflows a `u64`, or if the arguments are
7    /// not valid in conjunction with the `bytes_per_row` or `rows_per_image` parameters in
8    /// `self`.
9    ///
10    /// This is public for use by `wgpu-core` and `wgpu-hal`, it is not a stable API.
11    ///
12    /// Although WebGPU requires that `bytes_per_row` and `rows_per_image` be specified in
13    /// cases where they apply, we are more lenient here (although it's not clear if that is
14    /// necessary). Our caller, `validate_linear_texture_data`, enforces this and other
15    /// WebGPU requirements on the copy parameters that we do not check here.
16    #[doc(hidden)]
17    #[inline(always)]
18    pub fn get_buffer_texture_copy_info(
19        &self,
20        format: TextureFormat,
21        aspect: TextureAspect,
22        copy_size: &Extent3d,
23    ) -> Result<BufferTextureCopyInfo, Error> {
24        let copy_width = BufferAddress::from(copy_size.width);
25        let copy_height = BufferAddress::from(copy_size.height);
26        let depth_or_array_layers = BufferAddress::from(copy_size.depth_or_array_layers);
27
28        let block_size_bytes = BufferAddress::from(format.block_copy_size(Some(aspect)).unwrap());
29        let (block_width, block_height) = format.block_dimensions();
30        let block_width_texels = BufferAddress::from(block_width);
31        let block_height_texels = BufferAddress::from(block_height);
32
33        let width_blocks = copy_width.div_ceil(block_width_texels);
34        let height_blocks = copy_height.div_ceil(block_height_texels);
35
36        // The spec calls this bytesInLastRow.
37        let row_bytes_dense = width_blocks * block_size_bytes;
38        let row_stride_bytes = match self.bytes_per_row.map(BufferAddress::from) {
39            Some(bytes_per_row) if bytes_per_row >= row_bytes_dense => bytes_per_row,
40            Some(_) => return Err(Error::InvalidBytesPerRow),
41            None => row_bytes_dense,
42        };
43
44        let image_rows_dense = height_blocks;
45        let image_stride_rows = match self.rows_per_image.map(BufferAddress::from) {
46            Some(rows_per_image) if rows_per_image >= image_rows_dense => rows_per_image,
47            Some(_) => return Err(Error::InvalidRowsPerImage),
48            None => image_rows_dense,
49        };
50
51        let image_bytes_dense = match image_rows_dense.checked_sub(1) {
52            Some(rows_minus_one) => rows_minus_one
53                .checked_mul(row_stride_bytes)
54                .ok_or(Error::ImageBytesOverflow(false))?
55                .checked_add(row_bytes_dense)
56                .ok_or(Error::ImageBytesOverflow(true))?,
57            None => 0,
58        };
59
60        // It is possible that `image_stride_bytes` overflows, but the actual
61        // copy size does not, when the copy only has a single layer and
62        // `image_size_bytes` is not used. We don't worry about handling this
63        // gracefully because WebGPU texture size limits should keep things out
64        // of this realm entirely.
65        let image_stride_bytes = row_stride_bytes
66            .checked_mul(image_stride_rows)
67            .ok_or(Error::ImageStrideOverflow)?;
68
69        let bytes_in_copy = if depth_or_array_layers <= 1 {
70            depth_or_array_layers * image_bytes_dense
71        } else {
72            (depth_or_array_layers - 1)
73                .checked_mul(image_stride_bytes)
74                .ok_or(Error::ArraySizeOverflow(false))?
75                .checked_add(image_bytes_dense)
76                .ok_or(Error::ArraySizeOverflow(true))?
77        };
78
79        Ok(BufferTextureCopyInfo {
80            copy_width,
81            copy_height,
82            depth_or_array_layers,
83
84            offset: self.offset,
85
86            block_size_bytes,
87            block_width_texels,
88            block_height_texels,
89
90            width_blocks,
91            height_blocks,
92
93            row_bytes_dense,
94            row_stride_bytes,
95
96            image_stride_rows,
97            image_stride_bytes,
98
99            image_rows_dense,
100            image_bytes_dense,
101
102            bytes_in_copy,
103        })
104    }
105}
106
107/// Information about a copy between a buffer and a texture.
108///
109/// Mostly used for internal calculations, but useful nonetheless.
110/// Generated by [`TexelCopyBufferLayout::get_buffer_texture_copy_info`].
111#[derive(Debug, Clone, Copy, PartialEq, Eq)]
112pub struct BufferTextureCopyInfo {
113    /// The width of the copy region in pixels.
114    pub copy_width: u64,
115    /// The height of the copy region in pixels.
116    pub copy_height: u64,
117    /// The depth of the copy region in pixels.
118    pub depth_or_array_layers: u64,
119
120    /// The offset in the buffer where the copy starts.
121    pub offset: u64,
122
123    /// The size of a single texture texel block in bytes.
124    pub block_size_bytes: u64,
125    /// The number of texel in a texel block in the x direction.
126    pub block_width_texels: u64,
127    /// The number of texel in a texel block in the y direction.
128    pub block_height_texels: u64,
129
130    /// The width of the copy region in blocks.
131    pub width_blocks: u64,
132    /// The height of the copy region in blocks.
133    pub height_blocks: u64,
134
135    /// The number of bytes in the last row of the copy region.
136    pub row_bytes_dense: u64,
137    /// The stride in bytes between the start of one row in an image and the next row in the same image.
138    ///
139    /// This includes any padding between one row and the next row.
140    pub row_stride_bytes: u64,
141
142    /// The stride in rows between the start of one image and the next image.
143    pub image_stride_rows: u64,
144    /// The stride in bytes between the start of one image and the next image.
145    pub image_stride_bytes: u64,
146
147    /// The number of rows in a densely packed list of images.
148    ///
149    /// This is the number of rows in the image that are actually used for texel data,
150    /// and does not include any padding rows, unlike `image_stride_rows`.
151    pub image_rows_dense: u64,
152    /// The number of bytes in a densely packed list of images.
153    ///
154    /// This is the number of bytes in the image that are actually used for texel data,
155    /// or are used for padding between _rows_. Padding at the end of the last row and
156    /// between _images_ is not included.
157    pub image_bytes_dense: u64,
158
159    /// The total number of bytes in the copy region.
160    ///
161    /// This includes all padding except the padding after the last row in the copy.
162    pub bytes_in_copy: u64,
163}
164
165/// Errors that can occur while populating `BufferTextureCopyInfo`.
166//
167// We use the additional detail provided by these errors (over wgpu-core's
168// `TransferError`) to improve the reliability of the tests in this module. It
169// doesn't seem worth plumbing them upwards, because at the API level it
170// shouldn't be possible to exceed them without exceeding the WebGPU limits on
171// texture dimension. But the WebGPU limits are not currently enforced, so we
172// have to do something here to protect against overflows.
173//
174// Even when the WebGPU limits are enforced, it may still be useful to keep the
175// checks here as a failsafe if the correctness of the primary limit enforcement
176// is not immediately apparent.
177#[derive(Clone, Copy, Debug, Eq, PartialEq)]
178pub enum BufferTextureCopyInfoError {
179    /// The `bytes_per_row` is too small for the texture width.
180    InvalidBytesPerRow,
181    /// The `rows_per_image` is too small for the texture height.
182    InvalidRowsPerImage,
183    /// The image stride overflows a `u64`.
184    ImageStrideOverflow,
185    /// The last-layer byte size overflows a `u64`.
186    ///
187    /// The bool value indicates whether the multiplication (false) or the
188    /// addition (true) overflowed.
189    ImageBytesOverflow(bool),
190    /// The total size of the copy overflows a `u64`.
191    ///
192    /// The bool value indicates whether the multiplication (false) or the
193    /// addition (true) overflowed.
194    ArraySizeOverflow(bool),
195}
196type Error = BufferTextureCopyInfoError;
197
198#[cfg(test)]
199mod tests {
200    use super::*;
201
202    #[derive(Clone)]
203    struct LTDTest {
204        layout: TexelCopyBufferLayout,
205        format: TextureFormat,
206        aspect: TextureAspect,
207        copy_size: Extent3d,
208        expected_result: BufferTextureCopyInfo,
209        // Normally a Result<BufferTextureCopyInfo, Error> would be make sense,
210        // but since the existing tests were written to mutate
211        // `LTDTest.expected_result`, keeping this separate avoids a bunch of
212        // `unwrap`s.
213        expected_error: Option<Error>,
214    }
215
216    impl LTDTest {
217        #[track_caller]
218        fn run(&self) {
219            let linear_texture_data =
220                self.layout
221                    .get_buffer_texture_copy_info(self.format, self.aspect, &self.copy_size);
222            let expected = match self.expected_error {
223                Some(err) => Err(err),
224                None => Ok(self.expected_result),
225            };
226            assert_eq!(linear_texture_data, expected);
227        }
228    }
229
230    #[test]
231    fn linear_texture_data_1d_copy() {
232        let mut test = LTDTest {
233            layout: TexelCopyBufferLayout {
234                offset: 0,
235                bytes_per_row: None,
236                rows_per_image: None,
237            },
238            format: TextureFormat::Rgba8Unorm,
239            aspect: TextureAspect::All,
240            copy_size: Extent3d {
241                width: 4,
242                height: 1,
243                depth_or_array_layers: 1,
244            },
245            expected_result: BufferTextureCopyInfo {
246                copy_width: 4,
247                copy_height: 1,
248                depth_or_array_layers: 1,
249                offset: 0,
250                block_size_bytes: 4,
251                block_width_texels: 1,
252                block_height_texels: 1,
253                width_blocks: 4,
254                height_blocks: 1,
255                row_bytes_dense: 16,
256                row_stride_bytes: 16,
257                image_stride_rows: 1,
258                image_stride_bytes: 16,
259                image_rows_dense: 1,
260                image_bytes_dense: 16,
261                bytes_in_copy: 16,
262            },
263            expected_error: None,
264        };
265
266        test.run();
267
268        // Changing bytes_per_row should only change the bytes_per_row, not the bytes_in_copy
269        // as that is only affected by the last row size.
270        test.layout.bytes_per_row = Some(32);
271        test.expected_result.row_stride_bytes = 32;
272        test.expected_result.image_stride_bytes = 32;
273
274        test.run();
275
276        // Changing rows_per_image should only change the rows_per_image and bytes_per_image, nothing else
277        test.layout.rows_per_image = Some(4);
278        test.expected_result.image_stride_bytes = 128; // 32 * 4
279        test.expected_result.image_stride_rows = 4;
280
281        test.run();
282
283        // Changing the offset should change nothing.
284
285        test.layout.offset = 4;
286        test.expected_result.offset = 4;
287
288        test.run();
289    }
290
291    #[test]
292    fn linear_texture_data_2d_3d_copy() {
293        let template = LTDTest {
294            layout: TexelCopyBufferLayout {
295                offset: 0,
296                bytes_per_row: None,
297                rows_per_image: None,
298            },
299            format: TextureFormat::Rgba8Unorm,
300            aspect: TextureAspect::All,
301            copy_size: Extent3d {
302                width: 7,
303                height: 12,
304                depth_or_array_layers: 1,
305            },
306            expected_result: BufferTextureCopyInfo {
307                copy_width: 7,
308                copy_height: 12,
309                depth_or_array_layers: 1,
310                offset: 0,
311                block_size_bytes: 4,
312                block_width_texels: 1,
313                block_height_texels: 1,
314                width_blocks: 7,
315                height_blocks: 12,
316                row_bytes_dense: 4 * 7,
317                row_stride_bytes: 4 * 7,
318                image_stride_rows: 12,
319                image_stride_bytes: 4 * 7 * 12,
320                image_rows_dense: 12,
321                image_bytes_dense: 4 * 7 * 12,
322                bytes_in_copy: 4 * 7 * 12,
323            },
324            expected_error: None,
325        };
326
327        let mut test = template.clone();
328        test.run();
329
330        // Changing bytes_per_row changes a number of other properties.
331        test.layout.bytes_per_row = Some(48);
332        test.expected_result.row_stride_bytes = 48;
333        test.expected_result.image_stride_bytes = 48 * 12;
334        test.expected_result.image_bytes_dense = 48 * 11 + (4 * 7);
335        test.expected_result.bytes_in_copy = 48 * 11 + (4 * 7);
336        test.run();
337
338        // Making this a 3D copy only changes the depth_or_array_layers and the bytes_in_copy.
339        test.copy_size.depth_or_array_layers = 4;
340        test.expected_result.depth_or_array_layers = 4;
341        test.expected_result.bytes_in_copy = 48 * 12 * 3 + 48 * 11 + (4 * 7); // 4 layers
342        test.run();
343
344        // Changing rows_per_image
345        test.layout.rows_per_image = Some(20);
346        test.expected_result.image_stride_rows = 20;
347        test.expected_result.image_stride_bytes = 20 * test.expected_result.row_stride_bytes;
348        test.expected_result.bytes_in_copy = 48 * 20 * 3 + 48 * 11 + (4 * 7); // 4 layers
349        test.run();
350
351        // Invalid because the row stride is too small.
352        let mut test = template.clone();
353        test.layout.bytes_per_row = Some(20);
354        test.expected_error = Some(Error::InvalidBytesPerRow);
355        test.run();
356
357        // Invalid because the image stride is too small.
358        let mut test = template.clone();
359        test.layout.rows_per_image = Some(8);
360        test.expected_error = Some(Error::InvalidRowsPerImage);
361        test.run();
362
363        // Invalid because width * height * texel_size_bytes overflows.
364        let mut test = template.clone();
365        test.copy_size.width = u32::MAX;
366        test.copy_size.height = u32::MAX;
367        test.expected_error = Some(Error::ImageBytesOverflow(false));
368        test.run();
369
370        // Invalid because the addition of row_bytes_dense overflows.
371        // (But the product rows_minus_one * row_stride_bytes does not overflow.)
372        let mut test = template.clone();
373        test.copy_size.width = 0x8000_0000;
374        test.copy_size.height = 0x8000_0000;
375        test.expected_error = Some(Error::ImageBytesOverflow(true));
376        test.run();
377
378        // Invalid because image_stride_bytes overflows.
379        let mut test = template.clone();
380        test.copy_size.width = 0x8000_0000;
381        test.layout.rows_per_image = Some(0x8000_0000);
382        test.expected_result.image_stride_rows = 0x8000_0000;
383        test.expected_error = Some(Error::ImageStrideOverflow);
384        test.run();
385
386        // Invalid because (layers - 1) * image_stride_bytes overflows.
387        let mut test = template.clone();
388        test.copy_size.depth_or_array_layers = 0x8000_0000;
389        test.copy_size.width = 0x1_0000;
390        test.copy_size.height = 0x1_0000;
391        test.expected_error = Some(Error::ArraySizeOverflow(false));
392        test.run();
393
394        // Invalid because the total size of the copy overflows (but the product
395        // (layers - 1) * image_stride_bytes does not overflow).
396        let mut test = template.clone();
397        test.copy_size.depth_or_array_layers = 0x3fff_8001;
398        test.copy_size.width = 0x1_0001;
399        test.copy_size.height = 0x1_0001;
400        test.expected_error = Some(Error::ArraySizeOverflow(true));
401        test.run();
402    }
403
404    #[test]
405    fn linear_texture_data_2d_3d_compressed_copy() {
406        let mut test = LTDTest {
407            layout: TexelCopyBufferLayout {
408                offset: 0,
409                bytes_per_row: None,
410                rows_per_image: None,
411            },
412            format: TextureFormat::Bc1RgbaUnorm,
413            aspect: TextureAspect::All,
414            copy_size: Extent3d {
415                width: 7,
416                height: 13,
417                depth_or_array_layers: 1,
418            },
419            expected_result: BufferTextureCopyInfo {
420                copy_width: 7,
421                copy_height: 13,
422                depth_or_array_layers: 1,
423                offset: 0,
424                block_size_bytes: 8,
425                block_width_texels: 4,
426                block_height_texels: 4,
427                width_blocks: 2,
428                height_blocks: 4,
429                row_bytes_dense: 8 * 2, // block size * width_blocks
430                row_stride_bytes: 8 * 2,
431                image_stride_rows: 4,
432                image_stride_bytes: 8 * 2 * 4, // block size * width_blocks * height_blocks
433                image_rows_dense: 4,
434                image_bytes_dense: 8 * 2 * 4,
435                bytes_in_copy: 8 * 2 * 4,
436            },
437            expected_error: None,
438        };
439
440        test.run();
441
442        // Changing bytes_per_row.
443        test.layout.bytes_per_row = Some(48);
444        test.expected_result.row_stride_bytes = 48;
445        test.expected_result.image_stride_bytes = 48 * 4;
446        test.expected_result.image_bytes_dense = 48 * 3 + (8 * 2);
447        test.expected_result.bytes_in_copy = 48 * 3 + (8 * 2);
448
449        test.run();
450
451        // Changing rows_per_image.
452        test.layout.rows_per_image = Some(8);
453        test.expected_result.image_stride_bytes = 48 * 8;
454        test.expected_result.image_stride_rows = 8;
455
456        test.run();
457
458        // Making this a 3D copy only changes the depth_or_array_layers and the bytes_in_copy.
459        test.copy_size.depth_or_array_layers = 4;
460        test.expected_result.depth_or_array_layers = 4;
461        test.expected_result.bytes_in_copy = 48 * 8 * 3 + 48 * 3 + (8 * 2); // 4 layers
462
463        test.run();
464    }
465}