mirror of
https://github.com/gfx-rs/wgpu.git
synced 2026-04-22 03:02:01 -04:00
Implement buffer to any copy bounds checking
This commit is contained in:
@@ -15,6 +15,7 @@ use crate::{
|
||||
use hal::command::CommandBuffer as _;
|
||||
use wgt::{BufferAddress, BufferUsage, Extent3d, Origin3d, TextureDataLayout, TextureUsage};
|
||||
|
||||
use std::convert::TryInto as _;
|
||||
use std::iter;
|
||||
|
||||
pub(crate) const BITS_PER_BYTE: u32 = 8;
|
||||
@@ -82,6 +83,179 @@ impl TextureCopyView {
|
||||
}
|
||||
}
|
||||
|
||||
/// Function copied with minor modifications from webgpu standard https://gpuweb.github.io/gpuweb/#valid-texture-copy-range
|
||||
pub(crate) fn validate_linear_texture_data(
|
||||
layout: &TextureDataLayout,
|
||||
buffer_size: BufferAddress,
|
||||
bytes_per_texel: BufferAddress,
|
||||
copy_size: &Extent3d,
|
||||
) {
|
||||
// Convert all inputs to BufferAddress (u64) to prevent overflow issues
|
||||
let copy_width = copy_size.width as BufferAddress;
|
||||
let copy_height = copy_size.height as BufferAddress;
|
||||
let copy_depth = copy_size.depth as BufferAddress;
|
||||
|
||||
let offset = layout.offset;
|
||||
let rows_per_image = layout.rows_per_image as BufferAddress;
|
||||
let bytes_per_row = layout.bytes_per_row as BufferAddress;
|
||||
|
||||
// TODO: Once compressed textures are supported, these needs to be fixed
|
||||
let block_width: BufferAddress = 1;
|
||||
let block_height: BufferAddress = 1;
|
||||
let block_size = bytes_per_texel;
|
||||
|
||||
assert_eq!(
|
||||
copy_width % block_width,
|
||||
0,
|
||||
"Copy width {} must be a multiple of texture block width {}",
|
||||
copy_size.width,
|
||||
block_width,
|
||||
);
|
||||
assert_eq!(
|
||||
copy_height % block_height,
|
||||
0,
|
||||
"Copy height {} must be a multiple of texture block height {}",
|
||||
copy_size.height,
|
||||
block_height,
|
||||
);
|
||||
assert_eq!(
|
||||
rows_per_image % block_height,
|
||||
0,
|
||||
"Rows per image {} must be a multiple of image format block height {}",
|
||||
rows_per_image,
|
||||
block_height,
|
||||
);
|
||||
|
||||
let bytes_in_a_complete_row = block_size * copy_width / block_width;
|
||||
let required_bytes_in_copy = if copy_width == 0 || copy_height == 0 || copy_depth == 0 {
|
||||
0
|
||||
} else {
|
||||
let actual_rows_per_image = if rows_per_image == 0 {
|
||||
copy_height
|
||||
} else {
|
||||
rows_per_image
|
||||
};
|
||||
let texel_block_rows_per_image = actual_rows_per_image / block_height;
|
||||
let bytes_per_image = bytes_per_row * texel_block_rows_per_image;
|
||||
let bytes_in_last_slice =
|
||||
bytes_per_row * (copy_height / block_height - 1) + bytes_in_a_complete_row;
|
||||
bytes_per_image * (copy_depth - 1) + bytes_in_last_slice
|
||||
};
|
||||
|
||||
if rows_per_image != 0 {
|
||||
assert!(
|
||||
rows_per_image >= copy_height,
|
||||
"Rows per image {} must be greater or equal to copy_extent.height {}",
|
||||
rows_per_image,
|
||||
copy_height
|
||||
)
|
||||
}
|
||||
assert!(
|
||||
offset + required_bytes_in_copy <= buffer_size,
|
||||
"Texture copy using buffer indices {}..{} would overrun buffer of size {}",
|
||||
offset,
|
||||
offset + required_bytes_in_copy,
|
||||
buffer_size
|
||||
);
|
||||
assert_eq!(
|
||||
offset % block_size,
|
||||
0,
|
||||
"Buffer offset {} must be a multiple of image format block size {}",
|
||||
offset,
|
||||
block_size,
|
||||
);
|
||||
if copy_height > 1 {
|
||||
assert!(
|
||||
bytes_per_row >= bytes_in_a_complete_row,
|
||||
"Bytes per row {} must be at least the size of {} {}-byte texel blocks ({})",
|
||||
bytes_per_row,
|
||||
copy_width / block_width,
|
||||
block_size,
|
||||
bytes_in_a_complete_row,
|
||||
)
|
||||
}
|
||||
if copy_depth > 1 {
|
||||
assert_ne!(
|
||||
rows_per_image, 0,
|
||||
"Rows per image {} must be set to a non zero value when copy depth > 1 ({})",
|
||||
rows_per_image, copy_depth,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// Function copied with minor modifications from webgpu standard https://gpuweb.github.io/gpuweb/#valid-texture-copy-range
|
||||
pub(crate) fn validate_texture_copy_range(
|
||||
texture_copy_view: &TextureCopyView,
|
||||
texture_dimension: hal::image::Kind,
|
||||
copy_size: &Extent3d,
|
||||
) {
|
||||
// TODO: Once compressed textures are supported, these needs to be fixed
|
||||
let block_width: u32 = 1;
|
||||
let block_height: u32 = 1;
|
||||
|
||||
let mut extent = texture_dimension.level_extent(
|
||||
texture_copy_view
|
||||
.mip_level
|
||||
.try_into()
|
||||
.expect("Mip level must be < 256"),
|
||||
);
|
||||
match texture_dimension {
|
||||
hal::image::Kind::D1(..) => {
|
||||
assert_eq!(
|
||||
(copy_size.height, copy_size.depth),
|
||||
(1, 1),
|
||||
"Copies with 1D textures must have height and depth of 1. Currently: ({}, {})",
|
||||
copy_size.height,
|
||||
copy_size.depth,
|
||||
);
|
||||
}
|
||||
hal::image::Kind::D2(_, _, array_layers, _) => {
|
||||
extent.depth = array_layers as u32;
|
||||
}
|
||||
hal::image::Kind::D3(..) => {}
|
||||
};
|
||||
|
||||
let x_copy_max = texture_copy_view.origin.x + copy_size.width;
|
||||
assert!(
|
||||
x_copy_max <= extent.width,
|
||||
"Texture copy with X range {}..{} overruns texture width {}",
|
||||
texture_copy_view.origin.x,
|
||||
x_copy_max,
|
||||
extent.width,
|
||||
);
|
||||
let y_copy_max = texture_copy_view.origin.y + copy_size.height;
|
||||
assert!(
|
||||
y_copy_max <= extent.height,
|
||||
"Texture copy with Y range {}..{} overruns texture height {}",
|
||||
texture_copy_view.origin.y,
|
||||
y_copy_max,
|
||||
extent.height,
|
||||
);
|
||||
let z_copy_max = texture_copy_view.origin.z + copy_size.depth;
|
||||
assert!(
|
||||
z_copy_max <= extent.depth,
|
||||
"Texture copy with Z range {}..{} overruns texture depth {}",
|
||||
texture_copy_view.origin.z,
|
||||
z_copy_max,
|
||||
extent.depth,
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
copy_size.width % block_width,
|
||||
0,
|
||||
"Copy width {} must be a multiple of texture block width {}",
|
||||
copy_size.width,
|
||||
block_width,
|
||||
);
|
||||
assert_eq!(
|
||||
copy_size.height % block_height,
|
||||
0,
|
||||
"Copy height {} must be a multiple of texture block height {}",
|
||||
copy_size.height,
|
||||
block_height,
|
||||
);
|
||||
}
|
||||
|
||||
impl<G: GlobalIdentityHandlerFactory> Global<G> {
|
||||
pub fn command_encoder_copy_buffer_to_buffer<B: GfxBackend>(
|
||||
&self,
|
||||
@@ -114,6 +288,11 @@ impl<G: GlobalIdentityHandlerFactory> Global<G> {
|
||||
None => (),
|
||||
}
|
||||
|
||||
if size == 0 {
|
||||
log::trace!("Ignoring copy_buffer_to_buffer of size 0");
|
||||
return;
|
||||
}
|
||||
|
||||
let (src_buffer, src_pending) =
|
||||
cmb.trackers
|
||||
.buffers
|
||||
@@ -136,6 +315,47 @@ impl<G: GlobalIdentityHandlerFactory> Global<G> {
|
||||
);
|
||||
barriers.extend(dst_pending.map(|pending| pending.into_hal(dst_buffer)));
|
||||
|
||||
assert_eq!(
|
||||
size % wgt::COPY_BUFFER_ALIGNMENT,
|
||||
0,
|
||||
"Buffer copy size {} must be a multiple of {}",
|
||||
size,
|
||||
wgt::COPY_BUFFER_ALIGNMENT,
|
||||
);
|
||||
assert_eq!(
|
||||
source_offset % wgt::COPY_BUFFER_ALIGNMENT,
|
||||
0,
|
||||
"Buffer source offset {} must be a multiple of {}",
|
||||
source_offset,
|
||||
wgt::COPY_BUFFER_ALIGNMENT,
|
||||
);
|
||||
assert_eq!(
|
||||
destination_offset % wgt::COPY_BUFFER_ALIGNMENT,
|
||||
0,
|
||||
"Buffer destination offset {} must be a multiple of {}",
|
||||
destination_offset,
|
||||
wgt::COPY_BUFFER_ALIGNMENT,
|
||||
);
|
||||
|
||||
let source_start_offset = source_offset;
|
||||
let source_end_offset = source_offset + size;
|
||||
let destination_start_offset = destination_offset;
|
||||
let destination_end_offset = destination_offset + size;
|
||||
assert!(
|
||||
source_end_offset <= src_buffer.size,
|
||||
"Buffer to buffer copy with indices {}..{} overruns source buffer of size {}",
|
||||
source_start_offset,
|
||||
source_end_offset,
|
||||
src_buffer.size
|
||||
);
|
||||
assert!(
|
||||
destination_end_offset <= dst_buffer.size,
|
||||
"Buffer to buffer copy with indices {}..{} overruns destination buffer of size {}",
|
||||
destination_start_offset,
|
||||
destination_end_offset,
|
||||
dst_buffer.size
|
||||
);
|
||||
|
||||
let region = hal::command::BufferCopy {
|
||||
src: source_offset,
|
||||
dst: destination_offset,
|
||||
@@ -177,6 +397,11 @@ impl<G: GlobalIdentityHandlerFactory> Global<G> {
|
||||
None => (),
|
||||
}
|
||||
|
||||
if copy_size.width == 0 || copy_size.height == 0 || copy_size.width == 0 {
|
||||
log::trace!("Ignoring copy_buffer_to_texture of size 0");
|
||||
return;
|
||||
}
|
||||
|
||||
let (src_buffer, src_pending) = cmb.trackers.buffers.use_replace(
|
||||
&*buffer_guard,
|
||||
source.buffer,
|
||||
@@ -207,6 +432,14 @@ impl<G: GlobalIdentityHandlerFactory> Global<G> {
|
||||
source.layout.bytes_per_row,
|
||||
wgt::COPY_BYTES_PER_ROW_ALIGNMENT
|
||||
);
|
||||
validate_texture_copy_range(destination, dst_texture.kind, copy_size);
|
||||
validate_linear_texture_data(
|
||||
&source.layout,
|
||||
src_buffer.size,
|
||||
bytes_per_texel as BufferAddress,
|
||||
copy_size,
|
||||
);
|
||||
|
||||
let buffer_width = source.layout.bytes_per_row / bytes_per_texel;
|
||||
let region = hal::command::BufferImageCopy {
|
||||
buffer_offset: source.layout.offset,
|
||||
@@ -257,6 +490,11 @@ impl<G: GlobalIdentityHandlerFactory> Global<G> {
|
||||
None => (),
|
||||
}
|
||||
|
||||
if copy_size.width == 0 || copy_size.height == 0 || copy_size.width == 0 {
|
||||
log::trace!("Ignoring copy_texture_to_buffer of size 0");
|
||||
return;
|
||||
}
|
||||
|
||||
let (src_texture, src_pending) = cmb.trackers.textures.use_replace(
|
||||
&*texture_guard,
|
||||
source.texture,
|
||||
@@ -295,6 +533,14 @@ impl<G: GlobalIdentityHandlerFactory> Global<G> {
|
||||
destination.layout.bytes_per_row,
|
||||
wgt::COPY_BYTES_PER_ROW_ALIGNMENT
|
||||
);
|
||||
validate_texture_copy_range(source, src_texture.kind, copy_size);
|
||||
validate_linear_texture_data(
|
||||
&destination.layout,
|
||||
dst_buffer.size,
|
||||
bytes_per_texel as BufferAddress,
|
||||
copy_size,
|
||||
);
|
||||
|
||||
let buffer_width = destination.layout.bytes_per_row / bytes_per_texel;
|
||||
let region = hal::command::BufferImageCopy {
|
||||
buffer_offset: destination.layout.offset,
|
||||
@@ -351,6 +597,11 @@ impl<G: GlobalIdentityHandlerFactory> Global<G> {
|
||||
None => (),
|
||||
}
|
||||
|
||||
if copy_size.width == 0 || copy_size.height == 0 || copy_size.width == 0 {
|
||||
log::trace!("Ignoring copy_texture_to_texture of size 0");
|
||||
return;
|
||||
}
|
||||
|
||||
let (src_texture, src_pending) = cmb.trackers.textures.use_replace(
|
||||
&*texture_guard,
|
||||
source.texture,
|
||||
@@ -378,6 +629,9 @@ impl<G: GlobalIdentityHandlerFactory> Global<G> {
|
||||
barriers.extend(dst_pending.map(|pending| pending.into_hal(dst_texture)));
|
||||
|
||||
assert_eq!(src_texture.dimension, dst_texture.dimension);
|
||||
validate_texture_copy_range(source, src_texture.kind, copy_size);
|
||||
validate_texture_copy_range(destination, dst_texture.kind, copy_size);
|
||||
|
||||
let region = hal::command::ImageCopy {
|
||||
src_subresource: src_layers,
|
||||
src_offset,
|
||||
|
||||
@@ -153,7 +153,13 @@ impl<G: GlobalIdentityHandlerFactory> Global<G> {
|
||||
None => {}
|
||||
}
|
||||
|
||||
let mut stage = device.prepare_stage(data.len() as wgt::BufferAddress);
|
||||
let data_size = data.len() as wgt::BufferAddress;
|
||||
if data_size == 0 {
|
||||
log::trace!("Ignoring write_buffer of size 0");
|
||||
return;
|
||||
}
|
||||
|
||||
let mut stage = device.prepare_stage(data_size);
|
||||
{
|
||||
let mut mapped = stage
|
||||
.memory
|
||||
@@ -178,6 +184,30 @@ impl<G: GlobalIdentityHandlerFactory> Global<G> {
|
||||
let last_submit_index = device.life_guard.submission_index.load(Ordering::Relaxed);
|
||||
dst.life_guard.use_at(last_submit_index + 1);
|
||||
|
||||
assert_eq!(
|
||||
data_size % wgt::COPY_BUFFER_ALIGNMENT,
|
||||
0,
|
||||
"Buffer write size {} must be a multiple of {}",
|
||||
buffer_offset,
|
||||
wgt::COPY_BUFFER_ALIGNMENT,
|
||||
);
|
||||
assert_eq!(
|
||||
buffer_offset % wgt::COPY_BUFFER_ALIGNMENT,
|
||||
0,
|
||||
"Buffer offset {} must be a multiple of {}",
|
||||
buffer_offset,
|
||||
wgt::COPY_BUFFER_ALIGNMENT,
|
||||
);
|
||||
let destination_start_offset = buffer_offset;
|
||||
let destination_end_offset = buffer_offset + data_size;
|
||||
assert!(
|
||||
destination_end_offset <= dst.size,
|
||||
"Write buffer with indices {}..{} overruns destination buffer of size {}",
|
||||
destination_start_offset,
|
||||
destination_end_offset,
|
||||
dst.size
|
||||
);
|
||||
|
||||
let region = hal::command::BufferCopy {
|
||||
src: 0,
|
||||
dst: buffer_offset,
|
||||
@@ -233,11 +263,22 @@ impl<G: GlobalIdentityHandlerFactory> Global<G> {
|
||||
None => {}
|
||||
}
|
||||
|
||||
if size.width == 0 || size.height == 0 || size.width == 0 {
|
||||
log::trace!("Ignoring write_texture of size 0");
|
||||
return;
|
||||
}
|
||||
|
||||
let texture_format = texture_guard[destination.texture].format;
|
||||
let bytes_per_texel = conv::map_texture_format(texture_format, device.private_features)
|
||||
.surface_desc()
|
||||
.bits as u32
|
||||
/ BITS_PER_BYTE;
|
||||
crate::command::validate_linear_texture_data(
|
||||
data_layout,
|
||||
data.len() as wgt::BufferAddress,
|
||||
bytes_per_texel as wgt::BufferAddress,
|
||||
size,
|
||||
);
|
||||
|
||||
let bytes_per_row_alignment = get_lowest_common_denom(
|
||||
device.hal_limits.optimal_buffer_copy_pitch_alignment as u32,
|
||||
@@ -286,6 +327,7 @@ impl<G: GlobalIdentityHandlerFactory> Global<G> {
|
||||
"Write texture usage {:?} must contain flag COPY_DST",
|
||||
dst.usage
|
||||
);
|
||||
crate::command::validate_texture_copy_range(destination, dst.kind, size);
|
||||
|
||||
let last_submit_index = device.life_guard.submission_index.load(Ordering::Relaxed);
|
||||
dst.life_guard.use_at(last_submit_index + 1);
|
||||
|
||||
@@ -17,6 +17,8 @@ use std::{io, ptr, slice};
|
||||
pub const COPY_BYTES_PER_ROW_ALIGNMENT: u32 = 256;
|
||||
/// Bound uniform/storage buffer offsets must be aligned to this number.
|
||||
pub const BIND_BUFFER_ALIGNMENT: u64 = 256;
|
||||
/// Buffer to buffer copy offsets and sizes must be aligned to this number
|
||||
pub const COPY_BUFFER_ALIGNMENT: u64 = 4;
|
||||
|
||||
#[repr(transparent)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
|
||||
Reference in New Issue
Block a user