diff --git a/wgpu-core/src/command/transfer.rs b/wgpu-core/src/command/transfer.rs index 894afee544..c4d9073058 100644 --- a/wgpu-core/src/command/transfer.rs +++ b/wgpu-core/src/command/transfer.rs @@ -235,62 +235,64 @@ pub(crate) fn validate_linear_texture_data( copy_size: &Extent3d, need_copy_aligned_rows: bool, ) -> Result<(BufferAddress, BufferAddress), TransferError> { - // Convert all inputs to BufferAddress (u64) to avoid some of the overflow issues - // Note: u64 is not always enough to prevent overflow, especially when multiplying - // something with a potentially large depth value, so it is preferable to validate - // the copy size before calling this function (for example via `validate_texture_copy_range`). - let copy_width = copy_size.width as BufferAddress; - let copy_height = copy_size.height as BufferAddress; - let depth_or_array_layers = copy_size.depth_or_array_layers as BufferAddress; - - let offset = layout.offset; - - let block_size = format.block_copy_size(Some(aspect)).unwrap() as BufferAddress; - let (block_width, block_height) = format.block_dimensions(); - let block_width = block_width as BufferAddress; - let block_height = block_height as BufferAddress; - - if copy_width % block_width != 0 { + let wgt::BufferTextureCopyInfo { + copy_width, + copy_height, + depth_or_array_layers, + + offset, + + block_size_bytes, + block_width_texels, + block_height_texels, + + width_blocks: _, + height_blocks, + + row_bytes_dense, + row_stride_bytes, + + image_stride_rows: _, + image_stride_bytes, + + image_rows_dense: _, + image_bytes_dense: _, + + bytes_in_copy, + } = layout.get_buffer_texture_copy_info(format, aspect, copy_size); + + if copy_width % block_width_texels != 0 { return Err(TransferError::UnalignedCopyWidth); } - if copy_height % block_height != 0 { + if copy_height % block_height_texels != 0 { return Err(TransferError::UnalignedCopyHeight); } - let width_in_blocks = copy_width / block_width; - let height_in_blocks = copy_height / block_height; - - let bytes_in_last_row = width_in_blocks * block_size; + let requires_multiple_rows = depth_or_array_layers > 1 || height_blocks > 1; + let requires_multiple_images = depth_or_array_layers > 1; - let bytes_per_row = if let Some(bytes_per_row) = layout.bytes_per_row { - let bytes_per_row = bytes_per_row as BufferAddress; - if bytes_per_row < bytes_in_last_row { + if let Some(raw_bytes_per_row) = layout.bytes_per_row { + let raw_bytes_per_row = raw_bytes_per_row as BufferAddress; + if raw_bytes_per_row < row_bytes_dense { return Err(TransferError::InvalidBytesPerRow); } - bytes_per_row - } else { - if depth_or_array_layers > 1 || height_in_blocks > 1 { - return Err(TransferError::UnspecifiedBytesPerRow); - } - 0 - }; - let rows_per_image = if let Some(rows_per_image) = layout.rows_per_image { - let rows_per_image = rows_per_image as BufferAddress; - if rows_per_image < height_in_blocks { + } else if requires_multiple_rows { + return Err(TransferError::UnspecifiedBytesPerRow); + } + + if let Some(raw_rows_per_image) = layout.rows_per_image { + let raw_rows_per_image = raw_rows_per_image as BufferAddress; + if raw_rows_per_image < height_blocks { return Err(TransferError::InvalidRowsPerImage); } - rows_per_image - } else { - if depth_or_array_layers > 1 { - return Err(TransferError::UnspecifiedRowsPerImage); - } - 0 + } else if requires_multiple_images { + return Err(TransferError::UnspecifiedRowsPerImage); }; if need_copy_aligned_rows { let bytes_per_row_alignment = wgt::COPY_BYTES_PER_ROW_ALIGNMENT as BufferAddress; - let mut offset_alignment = block_size; + let mut offset_alignment = block_size_bytes; if format.is_depth_stencil_format() { offset_alignment = 4 } @@ -298,33 +300,23 @@ pub(crate) fn validate_linear_texture_data( return Err(TransferError::UnalignedBufferOffset(offset)); } - if bytes_per_row % bytes_per_row_alignment != 0 { + // The alignment of row_stride_bytes is only required if there are + // multiple rows + if requires_multiple_rows && row_stride_bytes % bytes_per_row_alignment != 0 { return Err(TransferError::UnalignedBytesPerRow); } } - let bytes_per_image = bytes_per_row * rows_per_image; - - let required_bytes_in_copy = if depth_or_array_layers == 0 { - 0 - } else { - let mut required_bytes_in_copy = bytes_per_image * (depth_or_array_layers - 1); - if height_in_blocks > 0 { - required_bytes_in_copy += bytes_per_row * (height_in_blocks - 1) + bytes_in_last_row; - } - required_bytes_in_copy - }; - - if offset + required_bytes_in_copy > buffer_size { + if offset + bytes_in_copy > buffer_size { return Err(TransferError::BufferOverrun { start_offset: offset, - end_offset: offset + required_bytes_in_copy, + end_offset: offset + bytes_in_copy, buffer_size, side: buffer_side, }); } - Ok((required_bytes_in_copy, bytes_per_image)) + Ok((bytes_in_copy, image_stride_bytes)) } /// WebGPU's [validating texture copy range][vtcr] algorithm. diff --git a/wgpu-types/src/lib.rs b/wgpu-types/src/lib.rs index 105e68e23d..7a3907f3fb 100644 --- a/wgpu-types/src/lib.rs +++ b/wgpu-types/src/lib.rs @@ -39,10 +39,12 @@ mod env; mod features; pub mod instance; pub mod math; +mod transfers; pub use counters::*; pub use features::*; pub use instance::*; +pub use transfers::*; /// Integral type used for [`Buffer`] offsets and sizes. /// diff --git a/wgpu-types/src/transfers.rs b/wgpu-types/src/transfers.rs new file mode 100644 index 0000000000..8ca32ce3bb --- /dev/null +++ b/wgpu-types/src/transfers.rs @@ -0,0 +1,324 @@ +use crate::{BufferAddress, Extent3d, TexelCopyBufferLayout, TextureAspect, TextureFormat}; + +impl TexelCopyBufferLayout { + /// Extract a variety of information about the given copy operation. + #[inline(always)] + pub fn get_buffer_texture_copy_info( + &self, + format: TextureFormat, + aspect: TextureAspect, + copy_size: &Extent3d, + ) -> BufferTextureCopyInfo { + // Convert all inputs to BufferAddress (u64) to avoid some of the overflow issues + // Note: u64 is not always enough to prevent overflow, especially when multiplying + // something with a potentially large depth value, so it is preferable to validate + // the copy size before calling this function (for example via `validate_texture_copy_range`). + let copy_width = copy_size.width as BufferAddress; + let copy_height = copy_size.height as BufferAddress; + let depth_or_array_layers = copy_size.depth_or_array_layers as BufferAddress; + + let block_size_bytes = format.block_copy_size(Some(aspect)).unwrap() as BufferAddress; + let (block_width, block_height) = format.block_dimensions(); + let block_width_texels = block_width as BufferAddress; + let block_height_texels = block_height as BufferAddress; + + let width_blocks = copy_width.div_ceil(block_width_texels); + let height_blocks = copy_height.div_ceil(block_height_texels); + + let row_bytes_dense = width_blocks * block_size_bytes; + + let row_stride_bytes = self.bytes_per_row.map_or(row_bytes_dense, u64::from); + let image_stride_rows = self.rows_per_image.map_or(height_blocks, u64::from); + + let image_stride_bytes = row_stride_bytes * image_stride_rows; + + let image_rows_dense = height_blocks; + let image_bytes_dense = + image_rows_dense.saturating_sub(1) * row_stride_bytes + row_bytes_dense; + + let mut bytes_in_copy = image_stride_bytes * depth_or_array_layers.saturating_sub(1); + if height_blocks > 0 { + bytes_in_copy += row_stride_bytes * (height_blocks - 1) + row_bytes_dense; + } + + BufferTextureCopyInfo { + copy_width, + copy_height, + depth_or_array_layers, + + offset: self.offset, + + block_size_bytes, + block_width_texels, + block_height_texels, + + width_blocks, + height_blocks, + + row_bytes_dense, + row_stride_bytes, + + image_stride_rows, + image_stride_bytes, + + image_rows_dense, + image_bytes_dense, + + bytes_in_copy, + } + } +} + +/// Information about a copy between a buffer and a texture. +/// +/// Mostly used for internal calculations, but useful nonetheless. +/// Generated by [`TexelCopyBufferLayout::get_buffer_texture_copy_info`]. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct BufferTextureCopyInfo { + /// The width of the copy region in pixels. + pub copy_width: u64, + /// The height of the copy region in pixels. + pub copy_height: u64, + /// The depth of the copy region in pixels. + pub depth_or_array_layers: u64, + + /// The offset in the buffer where the copy starts. + pub offset: u64, + + /// The size of a single texture texel block in bytes. + pub block_size_bytes: u64, + /// The number of texel in a texel block in the x direction. + pub block_width_texels: u64, + /// The number of texel in a texel block in the y direction. + pub block_height_texels: u64, + + /// The width of the copy region in blocks. + pub width_blocks: u64, + /// The height of the copy region in blocks. + pub height_blocks: u64, + + /// The number of bytes in the last row of the copy region. + pub row_bytes_dense: u64, + /// The stride in bytes between the start of one row in an image and the next row in the same image. + /// + /// This includes any padding between one row and the next row. + pub row_stride_bytes: u64, + + /// The stride in rows between the start of one image and the next image. + pub image_stride_rows: u64, + /// The stride in bytes between the start of one image and the next image. + pub image_stride_bytes: u64, + + /// The number of rows in a densely packed list of images. + /// + /// This is the number of rows in the image that are actually used for texel data, + /// and does not include any padding rows, unlike `image_stride_rows`. + pub image_rows_dense: u64, + /// The number of bytes in a densely packed list of images. + /// + /// This is the number of bytes in the image that are actually used for texel data, + /// or are used for padding between _rows_. Padding at the end of the last row and + /// between _images_ is not included. + pub image_bytes_dense: u64, + + /// The total number of bytes in the copy region. + /// + /// This includes all padding except the padding after the last row in the copy. + pub bytes_in_copy: u64, +} + +#[cfg(test)] +mod tests { + use super::*; + + struct LTDTest { + layout: TexelCopyBufferLayout, + format: TextureFormat, + aspect: TextureAspect, + copy_size: Extent3d, + expected_result: BufferTextureCopyInfo, + } + + impl LTDTest { + #[track_caller] + fn run(&self) { + let linear_texture_data = + self.layout + .get_buffer_texture_copy_info(self.format, self.aspect, &self.copy_size); + assert_eq!(linear_texture_data, self.expected_result); + } + } + + #[test] + fn linear_texture_data_1d_copy() { + let mut test = LTDTest { + layout: TexelCopyBufferLayout { + offset: 0, + bytes_per_row: None, + rows_per_image: None, + }, + format: TextureFormat::Rgba8Unorm, + aspect: TextureAspect::All, + copy_size: Extent3d { + width: 4, + height: 1, + depth_or_array_layers: 1, + }, + expected_result: BufferTextureCopyInfo { + copy_width: 4, + copy_height: 1, + depth_or_array_layers: 1, + offset: 0, + block_size_bytes: 4, + block_width_texels: 1, + block_height_texels: 1, + width_blocks: 4, + height_blocks: 1, + row_bytes_dense: 16, + row_stride_bytes: 16, + image_stride_rows: 1, + image_stride_bytes: 16, + image_rows_dense: 1, + image_bytes_dense: 16, + bytes_in_copy: 16, + }, + }; + + test.run(); + + // Changing bytes_per_row should only change the bytes_per_row, not the bytes_in_copy + // as that is only affected by the last row size. + test.layout.bytes_per_row = Some(32); + test.expected_result.row_stride_bytes = 32; + test.expected_result.image_stride_bytes = 32; + + test.run(); + + // Changing rows_per_image should only change the rows_per_image and bytes_per_image, nothing else + test.layout.rows_per_image = Some(4); + test.expected_result.image_stride_bytes = 128; // 32 * 4 + test.expected_result.image_stride_rows = 4; + + test.run(); + + // Changing the offset should change nothing. + + test.layout.offset = 4; + test.expected_result.offset = 4; + + test.run(); + } + + #[test] + fn linear_texture_data_2d_3d_copy() { + let mut test = LTDTest { + layout: TexelCopyBufferLayout { + offset: 0, + bytes_per_row: None, + rows_per_image: None, + }, + format: TextureFormat::Rgba8Unorm, + aspect: TextureAspect::All, + copy_size: Extent3d { + width: 7, + height: 12, + depth_or_array_layers: 1, + }, + expected_result: BufferTextureCopyInfo { + copy_width: 7, + copy_height: 12, + depth_or_array_layers: 1, + offset: 0, + block_size_bytes: 4, + block_width_texels: 1, + block_height_texels: 1, + width_blocks: 7, + height_blocks: 12, + row_bytes_dense: 4 * 7, + row_stride_bytes: 4 * 7, + image_stride_rows: 12, + image_stride_bytes: 4 * 7 * 12, + image_rows_dense: 12, + image_bytes_dense: 4 * 7 * 12, + bytes_in_copy: 4 * 7 * 12, + }, + }; + + test.run(); + + // Changing bytes_per_row changes a number of other properties. + test.layout.bytes_per_row = Some(48); + test.expected_result.row_stride_bytes = 48; + test.expected_result.image_stride_bytes = 48 * 12; + test.expected_result.image_bytes_dense = 48 * 11 + (4 * 7); + test.expected_result.bytes_in_copy = 48 * 11 + (4 * 7); + + // Making this a 3D copy only changes the depth_or_array_layers and the bytes_in_copy. + test.copy_size.depth_or_array_layers = 4; + test.expected_result.depth_or_array_layers = 4; + test.expected_result.bytes_in_copy = 48 * 12 * 3 + 48 * 11 + (4 * 7); // 4 layers + + test.run(); + } + + #[test] + fn linear_texture_data_2d_3d_compressed_copy() { + let mut test = LTDTest { + layout: TexelCopyBufferLayout { + offset: 0, + bytes_per_row: None, + rows_per_image: None, + }, + format: TextureFormat::Bc1RgbaUnorm, + aspect: TextureAspect::All, + copy_size: Extent3d { + width: 7, + height: 13, + depth_or_array_layers: 1, + }, + expected_result: BufferTextureCopyInfo { + copy_width: 7, + copy_height: 13, + depth_or_array_layers: 1, + offset: 0, + block_size_bytes: 8, + block_width_texels: 4, + block_height_texels: 4, + width_blocks: 2, + height_blocks: 4, + row_bytes_dense: 8 * 2, // block size * width_blocks + row_stride_bytes: 8 * 2, + image_stride_rows: 4, + image_stride_bytes: 8 * 2 * 4, // block size * width_blocks * height_blocks + image_rows_dense: 4, + image_bytes_dense: 8 * 2 * 4, + bytes_in_copy: 8 * 2 * 4, + }, + }; + + test.run(); + + // Changing bytes_per_row. + test.layout.bytes_per_row = Some(48); + test.expected_result.row_stride_bytes = 48; + test.expected_result.image_stride_bytes = 48 * 4; + test.expected_result.image_bytes_dense = 48 * 3 + (8 * 2); + test.expected_result.bytes_in_copy = 48 * 3 + (8 * 2); + + test.run(); + + // Changing rows_per_image. + test.layout.rows_per_image = Some(8); + test.expected_result.image_stride_bytes = 48 * 8; + test.expected_result.image_stride_rows = 8; + + test.run(); + + // Making this a 3D copy only changes the depth_or_array_layers and the bytes_in_copy. + test.copy_size.depth_or_array_layers = 4; + test.expected_result.depth_or_array_layers = 4; + test.expected_result.bytes_in_copy = 48 * 8 * 3 + 48 * 3 + (8 * 2); // 4 layers + + test.run(); + } +} diff --git a/wgpu/src/lib.rs b/wgpu/src/lib.rs index f81f65028f..d214d8a37e 100644 --- a/wgpu/src/lib.rs +++ b/wgpu/src/lib.rs @@ -70,24 +70,25 @@ pub use api::*; pub use wgt::{ AdapterInfo, AddressMode, AllocatorReport, AstcBlock, AstcChannel, Backend, BackendOptions, Backends, BindGroupLayoutEntry, BindingType, BlendComponent, BlendFactor, BlendOperation, - BlendState, BufferAddress, BufferBindingType, BufferSize, BufferTransition, BufferUsages, - BufferUses, Color, ColorTargetState, ColorWrites, CommandBufferDescriptor, CompareFunction, - CompositeAlphaMode, CopyExternalImageDestInfo, CoreCounters, DepthBiasState, DepthStencilState, - DeviceLostReason, DeviceType, DownlevelCapabilities, DownlevelFlags, DownlevelLimits, - Dx12BackendOptions, Dx12Compiler, DxcShaderModel, DynamicOffset, Extent3d, Face, Features, - FeaturesWGPU, FeaturesWebGPU, FilterMode, FrontFace, GlBackendOptions, GlFenceBehavior, - Gles3MinorVersion, HalCounters, ImageSubresourceRange, IndexFormat, InstanceDescriptor, - InstanceFlags, InternalCounters, Limits, MemoryBudgetThresholds, MemoryHints, MultisampleState, - NoopBackendOptions, Origin2d, Origin3d, PipelineStatisticsTypes, PollError, PollStatus, - PolygonMode, PowerPreference, PredefinedColorSpace, PresentMode, PresentationTimestamp, - PrimitiveState, PrimitiveTopology, PushConstantRange, QueryType, RenderBundleDepthStencil, - RequestAdapterError, SamplerBindingType, SamplerBorderColor, ShaderLocation, ShaderModel, - ShaderRuntimeChecks, ShaderStages, StencilFaceState, StencilOperation, StencilState, - StorageTextureAccess, SurfaceCapabilities, SurfaceStatus, TexelCopyBufferLayout, TextureAspect, - TextureDimension, TextureFormat, TextureFormatFeatureFlags, TextureFormatFeatures, - TextureSampleType, TextureTransition, TextureUsages, TextureUses, TextureViewDimension, Trace, - VertexAttribute, VertexFormat, VertexStepMode, WasmNotSend, WasmNotSendSync, WasmNotSync, - COPY_BUFFER_ALIGNMENT, COPY_BYTES_PER_ROW_ALIGNMENT, MAP_ALIGNMENT, PUSH_CONSTANT_ALIGNMENT, + BlendState, BufferAddress, BufferBindingType, BufferSize, BufferTextureCopyInfo, + BufferTransition, BufferUsages, BufferUses, Color, ColorTargetState, ColorWrites, + CommandBufferDescriptor, CompareFunction, CompositeAlphaMode, CopyExternalImageDestInfo, + CoreCounters, DepthBiasState, DepthStencilState, DeviceLostReason, DeviceType, + DownlevelCapabilities, DownlevelFlags, DownlevelLimits, Dx12BackendOptions, Dx12Compiler, + DxcShaderModel, DynamicOffset, Extent3d, Face, Features, FeaturesWGPU, FeaturesWebGPU, + FilterMode, FrontFace, GlBackendOptions, GlFenceBehavior, Gles3MinorVersion, HalCounters, + ImageSubresourceRange, IndexFormat, InstanceDescriptor, InstanceFlags, InternalCounters, + Limits, MemoryBudgetThresholds, MemoryHints, MultisampleState, NoopBackendOptions, Origin2d, + Origin3d, PipelineStatisticsTypes, PollError, PollStatus, PolygonMode, PowerPreference, + PredefinedColorSpace, PresentMode, PresentationTimestamp, PrimitiveState, PrimitiveTopology, + PushConstantRange, QueryType, RenderBundleDepthStencil, RequestAdapterError, + SamplerBindingType, SamplerBorderColor, ShaderLocation, ShaderModel, ShaderRuntimeChecks, + ShaderStages, StencilFaceState, StencilOperation, StencilState, StorageTextureAccess, + SurfaceCapabilities, SurfaceStatus, TexelCopyBufferLayout, TextureAspect, TextureDimension, + TextureFormat, TextureFormatFeatureFlags, TextureFormatFeatures, TextureSampleType, + TextureTransition, TextureUsages, TextureUses, TextureViewDimension, Trace, VertexAttribute, + VertexFormat, VertexStepMode, WasmNotSend, WasmNotSendSync, WasmNotSync, COPY_BUFFER_ALIGNMENT, + COPY_BYTES_PER_ROW_ALIGNMENT, MAP_ALIGNMENT, PUSH_CONSTANT_ALIGNMENT, QUERY_RESOLVE_BUFFER_ALIGNMENT, QUERY_SET_MAX_QUERIES, QUERY_SIZE, VERTEX_STRIDE_ALIGNMENT, }; #[expect(deprecated)]