@@ -2,46 +2,81 @@ use crate::{BufferAddress, Extent3d, TexelCopyBufferLayout, TextureAspect, Textu
22
33impl TexelCopyBufferLayout {
44 /// 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) ]
517 #[ inline( always) ]
618 pub fn get_buffer_texture_copy_info (
719 & self ,
820 format : TextureFormat ,
921 aspect : TextureAspect ,
1022 copy_size : & Extent3d ,
11- ) -> BufferTextureCopyInfo {
12- // Convert all inputs to BufferAddress (u64) to avoid some of the overflow issues
13- // Note: u64 is not always enough to prevent overflow, especially when multiplying
14- // something with a potentially large depth value, so it is preferable to validate
15- // the copy size before calling this function (for example via `validate_texture_copy_range`).
16- let copy_width = copy_size. width as BufferAddress ;
17- let copy_height = copy_size. height as BufferAddress ;
18- let depth_or_array_layers = copy_size. depth_or_array_layers as BufferAddress ;
19-
20- let block_size_bytes = format. block_copy_size ( Some ( aspect) ) . unwrap ( ) as BufferAddress ;
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 ( ) ) ;
2129 let ( block_width, block_height) = format. block_dimensions ( ) ;
22- let block_width_texels = block_width as BufferAddress ;
23- let block_height_texels = block_height as BufferAddress ;
30+ let block_width_texels = BufferAddress :: from ( block_width ) ;
31+ let block_height_texels = BufferAddress :: from ( block_height ) ;
2432
2533 let width_blocks = copy_width. div_ceil ( block_width_texels) ;
2634 let height_blocks = copy_height. div_ceil ( block_height_texels) ;
2735
36+ // The spec calls this bytesInLastRow.
2837 let row_bytes_dense = width_blocks * block_size_bytes;
29-
30- let row_stride_bytes = self . bytes_per_row . map_or ( row_bytes_dense, u64 :: from ) ;
31- let image_stride_rows = self . rows_per_image . map_or ( height_blocks , u64 :: from ) ;
32-
33- let image_stride_bytes = row_stride_bytes * image_stride_rows ;
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+ } ;
3443
3544 let image_rows_dense = height_blocks;
36- let image_bytes_dense =
37- image_rows_dense. saturating_sub ( 1 ) * row_stride_bytes + row_bytes_dense;
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+ } ;
3850
39- let mut bytes_in_copy = image_stride_bytes * depth_or_array_layers. saturating_sub ( 1 ) ;
40- if height_blocks > 0 {
41- bytes_in_copy += row_stride_bytes * ( height_blocks - 1 ) + row_bytes_dense;
42- }
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+ } ;
4378
44- BufferTextureCopyInfo {
79+ Ok ( BufferTextureCopyInfo {
4580 copy_width,
4681 copy_height,
4782 depth_or_array_layers,
@@ -65,7 +100,7 @@ impl TexelCopyBufferLayout {
65100 image_bytes_dense,
66101
67102 bytes_in_copy,
68- }
103+ } )
69104 }
70105}
71106
@@ -127,16 +162,55 @@ pub struct BufferTextureCopyInfo {
127162 pub bytes_in_copy : u64 ,
128163}
129164
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 ) ]
178+ pub 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+ }
196+ type Error = BufferTextureCopyInfoError ;
197+
130198#[ cfg( test) ]
131199mod tests {
132200 use super :: * ;
133201
202+ #[ derive( Clone ) ]
134203 struct LTDTest {
135204 layout : TexelCopyBufferLayout ,
136205 format : TextureFormat ,
137206 aspect : TextureAspect ,
138207 copy_size : Extent3d ,
139208 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 > ,
140214 }
141215
142216 impl LTDTest {
@@ -145,7 +219,11 @@ mod tests {
145219 let linear_texture_data =
146220 self . layout
147221 . get_buffer_texture_copy_info ( self . format , self . aspect , & self . copy_size ) ;
148- assert_eq ! ( linear_texture_data, self . expected_result) ;
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) ;
149227 }
150228 }
151229
@@ -182,6 +260,7 @@ mod tests {
182260 image_bytes_dense : 16 ,
183261 bytes_in_copy : 16 ,
184262 } ,
263+ expected_error : None ,
185264 } ;
186265
187266 test. run ( ) ;
@@ -211,7 +290,7 @@ mod tests {
211290
212291 #[ test]
213292 fn linear_texture_data_2d_3d_copy ( ) {
214- let mut test = LTDTest {
293+ let template = LTDTest {
215294 layout : TexelCopyBufferLayout {
216295 offset : 0 ,
217296 bytes_per_row : None ,
@@ -242,8 +321,10 @@ mod tests {
242321 image_bytes_dense : 4 * 7 * 12 ,
243322 bytes_in_copy : 4 * 7 * 12 ,
244323 } ,
324+ expected_error : None ,
245325 } ;
246326
327+ let mut test = template. clone ( ) ;
247328 test. run ( ) ;
248329
249330 // Changing bytes_per_row changes a number of other properties.
@@ -252,12 +333,71 @@ mod tests {
252333 test. expected_result . image_stride_bytes = 48 * 12 ;
253334 test. expected_result . image_bytes_dense = 48 * 11 + ( 4 * 7 ) ;
254335 test. expected_result . bytes_in_copy = 48 * 11 + ( 4 * 7 ) ;
336+ test. run ( ) ;
255337
256338 // Making this a 3D copy only changes the depth_or_array_layers and the bytes_in_copy.
257339 test. copy_size . depth_or_array_layers = 4 ;
258340 test. expected_result . depth_or_array_layers = 4 ;
259341 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 ( ) ;
260393
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 ) ) ;
261401 test. run ( ) ;
262402 }
263403
@@ -294,6 +434,7 @@ mod tests {
294434 image_bytes_dense : 8 * 2 * 4 ,
295435 bytes_in_copy : 8 * 2 * 4 ,
296436 } ,
437+ expected_error : None ,
297438 } ;
298439
299440 test. run ( ) ;
0 commit comments