@@ -363,6 +363,70 @@ pub(crate) fn validate_linear_texture_data(
363363 Ok ( ( bytes_in_copy, image_stride_bytes) )
364364}
365365
366+ /// Validation for texture/buffer copies.
367+ ///
368+ /// This implements the following checks from WebGPU's [validating texture buffer copy][vtbc]
369+ /// algorithm:
370+ /// * The texture must not be multisampled.
371+ /// * The copy must be from/to a single aspect of the texture.
372+ /// * If `aligned` is true, the buffer offset must be aligned appropriately.
373+ ///
374+ /// The following steps in the algorithm are implemented elsewhere:
375+ /// * Invocation of other validation algorithms.
376+ /// * The texture usage (COPY_DST / COPY_SRC) check.
377+ /// * The check for non-copyable depth/stencil formats. The caller must perform
378+ /// this check using `is_valid_copy_src_format` / `is_valid_copy_dst_format`
379+ /// before calling this function. This function will panic if
380+ /// [`wgt::TextureFormat::block_copy_size`] returns `None` due to a
381+ /// non-copyable format.
382+ ///
383+ /// [vtbc]: https://gpuweb.github.io/gpuweb/#abstract-opdef-validating-texture-buffer-copy
384+ pub ( crate ) fn validate_texture_buffer_copy < T > (
385+ texture_copy_view : & wgt:: TexelCopyTextureInfo < T > ,
386+ aspect : hal:: FormatAspects ,
387+ desc : & wgt:: TextureDescriptor < ( ) , Vec < wgt:: TextureFormat > > ,
388+ offset : BufferAddress ,
389+ aligned : bool ,
390+ ) -> Result < ( ) , TransferError > {
391+ if desc. sample_count != 1 {
392+ return Err ( TransferError :: InvalidSampleCount {
393+ sample_count : desc. sample_count ,
394+ } ) ;
395+ }
396+
397+ if !aspect. is_one ( ) {
398+ return Err ( TransferError :: CopyAspectNotOne ) ;
399+ }
400+
401+ let mut offset_alignment = if desc. format . is_depth_stencil_format ( ) {
402+ 4
403+ } else {
404+ // The case where `block_copy_size` returns `None` is currently
405+ // unreachable both for the reason in the expect message, and also
406+ // because the currently-defined non-copyable formats are depth/stencil
407+ // formats so would take the `if` branch.
408+ desc. format
409+ . block_copy_size ( Some ( texture_copy_view. aspect ) )
410+ . expect ( "non-copyable formats should have been rejected previously" )
411+ } ;
412+
413+ // TODO(https://github.com/gfx-rs/wgpu/issues/7947): This does not match the spec.
414+ // The spec imposes no alignment requirement if `!aligned`, and otherwise
415+ // imposes only the `offset_alignment` as calculated above. wgpu currently
416+ // can panic on alignments <4B, so we reject them here to avoid a panic.
417+ if aligned {
418+ offset_alignment = offset_alignment. max ( 4 ) ;
419+ } else {
420+ offset_alignment = 4 ;
421+ }
422+
423+ if offset % u64:: from ( offset_alignment) != 0 {
424+ return Err ( TransferError :: UnalignedBufferOffset ( offset) ) ;
425+ }
426+
427+ Ok ( ( ) )
428+ }
429+
366430/// Validate the extent and alignment of a texture copy.
367431///
368432/// Copied with minor modifications from WebGPU standard. This mostly follows
@@ -884,10 +948,6 @@ impl Global {
884948 . map ( |pending| pending. into_hal ( dst_raw) )
885949 . collect :: < Vec < _ > > ( ) ;
886950
887- if !dst_base. aspect . is_one ( ) {
888- return Err ( TransferError :: CopyAspectNotOne . into ( ) ) ;
889- }
890-
891951 if !conv:: is_valid_copy_dst_texture_format ( dst_texture. desc . format , destination. aspect )
892952 {
893953 return Err ( TransferError :: CopyToForbiddenTextureFormat {
@@ -897,6 +957,14 @@ impl Global {
897957 . into ( ) ) ;
898958 }
899959
960+ validate_texture_buffer_copy (
961+ destination,
962+ dst_base. aspect ,
963+ & dst_texture. desc ,
964+ source. layout . offset ,
965+ true , // alignment required for buffer offset
966+ ) ?;
967+
900968 let ( required_buffer_bytes_in_copy, bytes_per_array_layer) =
901969 validate_linear_texture_data (
902970 & source. layout ,
@@ -999,12 +1067,7 @@ impl Global {
9991067 src_texture
10001068 . check_usage ( TextureUsages :: COPY_SRC )
10011069 . map_err ( TransferError :: MissingTextureUsage ) ?;
1002- if src_texture. desc . sample_count != 1 {
1003- return Err ( TransferError :: InvalidSampleCount {
1004- sample_count : src_texture. desc . sample_count ,
1005- }
1006- . into ( ) ) ;
1007- }
1070+
10081071 if source. mip_level >= src_texture. desc . mip_level_count {
10091072 return Err ( TransferError :: InvalidMipLevel {
10101073 requested : source. mip_level ,
@@ -1013,10 +1076,6 @@ impl Global {
10131076 . into ( ) ) ;
10141077 }
10151078
1016- if !src_base. aspect . is_one ( ) {
1017- return Err ( TransferError :: CopyAspectNotOne . into ( ) ) ;
1018- }
1019-
10201079 if !conv:: is_valid_copy_src_texture_format ( src_texture. desc . format , source. aspect ) {
10211080 return Err ( TransferError :: CopyFromForbiddenTextureFormat {
10221081 format : src_texture. desc . format ,
@@ -1025,6 +1084,14 @@ impl Global {
10251084 . into ( ) ) ;
10261085 }
10271086
1087+ validate_texture_buffer_copy (
1088+ source,
1089+ src_base. aspect ,
1090+ & src_texture. desc ,
1091+ destination. layout . offset ,
1092+ true , // alignment required for buffer offset
1093+ ) ?;
1094+
10281095 let ( required_buffer_bytes_in_copy, bytes_per_array_layer) =
10291096 validate_linear_texture_data (
10301097 & destination. layout ,
0 commit comments