@@ -2,46 +2,81 @@ use crate::{BufferAddress, Extent3d, TexelCopyBufferLayout, TextureAspect, Textu
2
2
3
3
impl TexelCopyBufferLayout {
4
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) ]
5
17
#[ inline( always) ]
6
18
pub fn get_buffer_texture_copy_info (
7
19
& self ,
8
20
format : TextureFormat ,
9
21
aspect : TextureAspect ,
10
22
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 ( ) ) ;
21
29
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 ) ;
24
32
25
33
let width_blocks = copy_width. div_ceil ( block_width_texels) ;
26
34
let height_blocks = copy_height. div_ceil ( block_height_texels) ;
27
35
36
+ // The spec calls this bytesInLastRow.
28
37
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
+ } ;
34
43
35
44
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
+ } ;
38
50
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
+ } ;
43
78
44
- BufferTextureCopyInfo {
79
+ Ok ( BufferTextureCopyInfo {
45
80
copy_width,
46
81
copy_height,
47
82
depth_or_array_layers,
@@ -65,7 +100,7 @@ impl TexelCopyBufferLayout {
65
100
image_bytes_dense,
66
101
67
102
bytes_in_copy,
68
- }
103
+ } )
69
104
}
70
105
}
71
106
@@ -127,16 +162,55 @@ pub struct BufferTextureCopyInfo {
127
162
pub bytes_in_copy : u64 ,
128
163
}
129
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 ) ]
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
+
130
198
#[ cfg( test) ]
131
199
mod tests {
132
200
use super :: * ;
133
201
202
+ #[ derive( Clone ) ]
134
203
struct LTDTest {
135
204
layout : TexelCopyBufferLayout ,
136
205
format : TextureFormat ,
137
206
aspect : TextureAspect ,
138
207
copy_size : Extent3d ,
139
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 > ,
140
214
}
141
215
142
216
impl LTDTest {
@@ -145,7 +219,11 @@ mod tests {
145
219
let linear_texture_data =
146
220
self . layout
147
221
. 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) ;
149
227
}
150
228
}
151
229
@@ -182,6 +260,7 @@ mod tests {
182
260
image_bytes_dense : 16 ,
183
261
bytes_in_copy : 16 ,
184
262
} ,
263
+ expected_error : None ,
185
264
} ;
186
265
187
266
test. run ( ) ;
@@ -211,7 +290,7 @@ mod tests {
211
290
212
291
#[ test]
213
292
fn linear_texture_data_2d_3d_copy ( ) {
214
- let mut test = LTDTest {
293
+ let template = LTDTest {
215
294
layout : TexelCopyBufferLayout {
216
295
offset : 0 ,
217
296
bytes_per_row : None ,
@@ -242,8 +321,10 @@ mod tests {
242
321
image_bytes_dense : 4 * 7 * 12 ,
243
322
bytes_in_copy : 4 * 7 * 12 ,
244
323
} ,
324
+ expected_error : None ,
245
325
} ;
246
326
327
+ let mut test = template. clone ( ) ;
247
328
test. run ( ) ;
248
329
249
330
// Changing bytes_per_row changes a number of other properties.
@@ -252,12 +333,71 @@ mod tests {
252
333
test. expected_result . image_stride_bytes = 48 * 12 ;
253
334
test. expected_result . image_bytes_dense = 48 * 11 + ( 4 * 7 ) ;
254
335
test. expected_result . bytes_in_copy = 48 * 11 + ( 4 * 7 ) ;
336
+ test. run ( ) ;
255
337
256
338
// Making this a 3D copy only changes the depth_or_array_layers and the bytes_in_copy.
257
339
test. copy_size . depth_or_array_layers = 4 ;
258
340
test. expected_result . depth_or_array_layers = 4 ;
259
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 ( ) ;
260
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 ) ) ;
261
401
test. run ( ) ;
262
402
}
263
403
@@ -294,6 +434,7 @@ mod tests {
294
434
image_bytes_dense : 8 * 2 * 4 ,
295
435
bytes_in_copy : 8 * 2 * 4 ,
296
436
} ,
437
+ expected_error : None ,
297
438
} ;
298
439
299
440
test. run ( ) ;
0 commit comments