diff --git a/wgpu-core/src/command/transfer.rs b/wgpu-core/src/command/transfer.rs index f4c89c53b8..60847375bf 100644 --- a/wgpu-core/src/command/transfer.rs +++ b/wgpu-core/src/command/transfer.rs @@ -233,6 +233,13 @@ pub(crate) fn validate_texture_copy_range( let block_height = block_height as u32; let mut extent = texture_dimension.level_extent(texture_copy_view.mip_level as u8); + + // Adjust extent for the physical size of mips + if texture_copy_view.mip_level != 0 { + extent.width = conv::align_up(extent.width, block_width); + extent.height = conv::align_up(extent.height, block_height); + } + match texture_dimension { hal::image::Kind::D1(..) => { if (copy_size.height, copy_size.depth) != (1, 1) { @@ -512,6 +519,17 @@ impl Global { ))? } + // WebGPU uses the physical size of the texture for copies whereas vulkan uses + // the virtual size. We have passed validation, so it's safe to use the + // image extent data directly. We want the provided copy size to be no larger than + // the virtual size. + let max_image_extent = dst_texture.kind.level_extent(destination.mip_level as _); + let image_extent = Extent3d { + width: copy_size.width.min(max_image_extent.width), + height: copy_size.height.min(max_image_extent.height), + depth: copy_size.depth, + }; + let buffer_width = (source.layout.bytes_per_row / bytes_per_block) * block_width as u32; let region = hal::command::BufferImageCopy { buffer_offset: source.layout.offset, @@ -519,7 +537,7 @@ impl Global { buffer_height: source.layout.rows_per_image, image_layers: dst_layers, image_offset: dst_offset, - image_extent: conv::map_extent(copy_size, dst_texture.dimension), + image_extent: conv::map_extent(&image_extent, dst_texture.dimension), }; let cmb_raw = cmd_buf.raw.last_mut().unwrap(); unsafe { @@ -641,6 +659,17 @@ impl Global { ))? } + // WebGPU uses the physical size of the texture for copies whereas vulkan uses + // the virtual size. We have passed validation, so it's safe to use the + // image extent data directly. We want the provided copy size to be no larger than + // the virtual size. + let max_image_extent = src_texture.kind.level_extent(source.mip_level as _); + let image_extent = Extent3d { + width: copy_size.width.min(max_image_extent.width), + height: copy_size.height.min(max_image_extent.height), + depth: copy_size.depth, + }; + let buffer_width = (destination.layout.bytes_per_row / bytes_per_block) * block_width as u32; let region = hal::command::BufferImageCopy { @@ -649,7 +678,7 @@ impl Global { buffer_height: destination.layout.rows_per_image, image_layers: src_layers, image_offset: src_offset, - image_extent: conv::map_extent(copy_size, src_texture.dimension), + image_extent: conv::map_extent(&image_extent, src_texture.dimension), }; let cmb_raw = cmd_buf.raw.last_mut().unwrap(); unsafe { @@ -765,12 +794,28 @@ impl Global { copy_size, )?; + // WebGPU uses the physical size of the texture for copies whereas vulkan uses + // the virtual size. We have passed validation, so it's safe to use the + // image extent data directly. We want the provided copy size to be no larger than + // the virtual size. + let max_src_image_extent = src_texture.kind.level_extent(source.mip_level as _); + let max_dst_image_extent = dst_texture.kind.level_extent(destination.mip_level as _); + let image_extent = Extent3d { + width: copy_size + .width + .min(max_src_image_extent.width.min(max_dst_image_extent.width)), + height: copy_size + .height + .min(max_src_image_extent.height.min(max_dst_image_extent.height)), + depth: copy_size.depth, + }; + let region = hal::command::ImageCopy { src_subresource: src_layers, src_offset, dst_subresource: dst_layers, dst_offset, - extent: conv::map_extent(copy_size, src_texture.dimension), + extent: conv::map_extent(&image_extent, src_texture.dimension), }; let cmb_raw = cmd_buf.raw.last_mut().unwrap(); unsafe { diff --git a/wgpu-core/src/conv.rs b/wgpu-core/src/conv.rs index d13b55bdac..60374e2ab8 100644 --- a/wgpu-core/src/conv.rs +++ b/wgpu-core/src/conv.rs @@ -755,3 +755,16 @@ pub fn map_index_format(index_format: wgt::IndexFormat) -> hal::IndexType { wgt::IndexFormat::Uint32 => hal::IndexType::U32, } } + +/// Take `value` and round it up to the nearest alignment `alignment`. +/// +/// ```text +/// (0, 3) -> 0 +/// (1, 3) -> 3 +/// (2, 3) -> 3 +/// (3, 3) -> 3 +/// (4, 3) -> 6 +/// ... +pub fn align_up(value: u32, alignment: u32) -> u32 { + ((value + alignment - 1) / alignment) * alignment +} diff --git a/wgpu-core/src/device/queue.rs b/wgpu-core/src/device/queue.rs index b1a75cc807..d6c7518093 100644 --- a/wgpu-core/src/device/queue.rs +++ b/wgpu-core/src/device/queue.rs @@ -409,13 +409,24 @@ impl Global { stage.memory.flush_range(&device.raw, 0, None)?; } + // WebGPU uses the physical size of the texture for copies whereas vulkan uses + // the virtual size. We have passed validation, so it's safe to use the + // image extent data directly. We want the provided copy size to be no larger than + // the virtual size. + let max_image_extent = dst.kind.level_extent(destination.mip_level as _); + let image_extent = wgt::Extent3d { + width: size.width.min(max_image_extent.width), + height: size.height.min(max_image_extent.height), + depth: size.depth, + }; + let region = hal::command::BufferImageCopy { buffer_offset: 0, buffer_width: (stage_bytes_per_row / bytes_per_block) * block_width, buffer_height: texel_rows_per_image, image_layers, image_offset, - image_extent: conv::map_extent(size, dst.dimension), + image_extent: conv::map_extent(&image_extent, dst.dimension), }; unsafe { stage.cmdbuf.pipeline_barrier(