Restore plumbing of implicit remainder-of-buffer size to backends

This commit is contained in:
Andy Leiserson 2025-07-09 11:31:06 -07:00 committed by Jim Blandy
parent 00406a75a4
commit 468632f207
19 changed files with 144 additions and 114 deletions

View File

@ -27,6 +27,8 @@ webgpu:api,validation,encoding,cmds,copyTextureToTexture:sample_count:*
//FAIL: webgpu:api,validation,encoding,cmds,copyTextureToTexture:copy_ranges_with_compressed_texture_formats:* //FAIL: webgpu:api,validation,encoding,cmds,copyTextureToTexture:copy_ranges_with_compressed_texture_formats:*
webgpu:api,validation,encoding,cmds,index_access:* webgpu:api,validation,encoding,cmds,index_access:*
//FAIL: webgpu:api,validation,encoding,cmds,render,draw:* //FAIL: webgpu:api,validation,encoding,cmds,render,draw:*
webgpu:api,validation,encoding,cmds,render,draw:index_buffer_OOB:*
webgpu:api,validation,encoding,cmds,render,draw:unused_buffer_bound:*
webgpu:api,validation,encoding,encoder_state:* webgpu:api,validation,encoding,encoder_state:*
webgpu:api,validation,encoding,encoder_open_state:non_pass_commands:* webgpu:api,validation,encoding,encoder_open_state:non_pass_commands:*
webgpu:api,validation,encoding,encoder_open_state:render_pass_commands:* webgpu:api,validation,encoding,encoder_open_state:render_pass_commands:*

View File

@ -505,7 +505,7 @@ impl RenderBundleEncoder {
buffer_id, buffer_id,
index_format, index_format,
offset, offset,
size: size.map(NonZeroU64::get), size,
}); });
} }
} }
@ -610,7 +610,7 @@ fn set_index_buffer(
buffer_id: id::Id<id::markers::Buffer>, buffer_id: id::Id<id::markers::Buffer>,
index_format: wgt::IndexFormat, index_format: wgt::IndexFormat,
offset: u64, offset: u64,
size: Option<wgt::BufferSizeOrZero>, size: Option<NonZeroU64>,
) -> Result<(), RenderBundleErrorInner> { ) -> Result<(), RenderBundleErrorInner> {
let buffer = buffer_guard.get(buffer_id).get()?; let buffer = buffer_guard.get(buffer_id).get()?;
@ -642,7 +642,7 @@ fn set_vertex_buffer(
slot: u32, slot: u32,
buffer_id: id::Id<id::markers::Buffer>, buffer_id: id::Id<id::markers::Buffer>,
offset: u64, offset: u64,
size: Option<wgt::BufferSizeOrZero>, size: Option<NonZeroU64>,
) -> Result<(), RenderBundleErrorInner> { ) -> Result<(), RenderBundleErrorInner> {
let max_vertex_buffers = state.device.limits.max_vertex_buffers; let max_vertex_buffers = state.device.limits.max_vertex_buffers;
if slot >= max_vertex_buffers { if slot >= max_vertex_buffers {
@ -967,11 +967,7 @@ impl RenderBundle {
let bb = unsafe { let bb = unsafe {
// SAFETY: The binding size was checked against the buffer size // SAFETY: The binding size was checked against the buffer size
// in `set_index_buffer` and again in `IndexState::flush`. // in `set_index_buffer` and again in `IndexState::flush`.
hal::BufferBinding::new_unchecked( hal::BufferBinding::new_unchecked(buffer, *offset, *size)
buffer,
*offset,
size.expect("size was resolved in `RenderBundleEncoder::finish`"),
)
}; };
unsafe { raw.set_index_buffer(bb, *index_format) }; unsafe { raw.set_index_buffer(bb, *index_format) };
} }
@ -985,11 +981,7 @@ impl RenderBundle {
let bb = unsafe { let bb = unsafe {
// SAFETY: The binding size was checked against the buffer size // SAFETY: The binding size was checked against the buffer size
// in `set_vertex_buffer` and again in `VertexState::flush`. // in `set_vertex_buffer` and again in `VertexState::flush`.
hal::BufferBinding::new_unchecked( hal::BufferBinding::new_unchecked(buffer, *offset, *size)
buffer,
*offset,
size.expect("size was resolved in `RenderBundleEncoder::finish`"),
)
}; };
unsafe { raw.set_vertex_buffer(*slot, bb) }; unsafe { raw.set_vertex_buffer(*slot, bb) };
} }
@ -1176,7 +1168,7 @@ impl IndexState {
buffer: self.buffer.clone(), buffer: self.buffer.clone(),
index_format: self.format, index_format: self.format,
offset: self.range.start, offset: self.range.start,
size: Some(binding_size), size: NonZeroU64::new(binding_size),
}) })
} else { } else {
None None
@ -1232,7 +1224,7 @@ impl VertexState {
slot, slot,
buffer: self.buffer.clone(), buffer: self.buffer.clone(),
offset: self.range.start, offset: self.range.start,
size: Some(binding_size), size: NonZeroU64::new(binding_size),
}) })
} else { } else {
None None
@ -1596,7 +1588,7 @@ where
pub mod bundle_ffi { pub mod bundle_ffi {
use super::{RenderBundleEncoder, RenderCommand}; use super::{RenderBundleEncoder, RenderCommand};
use crate::{id, RawString}; use crate::{id, RawString};
use core::{convert::TryInto, num::NonZeroU64, slice}; use core::{convert::TryInto, slice};
use wgt::{BufferAddress, BufferSize, DynamicOffset, IndexFormat}; use wgt::{BufferAddress, BufferSize, DynamicOffset, IndexFormat};
/// # Safety /// # Safety
@ -1655,7 +1647,7 @@ pub mod bundle_ffi {
slot, slot,
buffer_id, buffer_id,
offset, offset,
size: size.map(NonZeroU64::get), size,
}); });
} }

View File

@ -1,17 +1,12 @@
use alloc::{borrow::Cow, sync::Arc, vec::Vec}; use alloc::{borrow::Cow, sync::Arc, vec::Vec};
use core::{ use core::{fmt, num::NonZeroU32, ops::Range, str};
fmt,
num::{NonZeroU32, NonZeroU64},
str,
};
use hal::ShouldBeNonZeroExt;
use arrayvec::ArrayVec; use arrayvec::ArrayVec;
use thiserror::Error; use thiserror::Error;
use wgt::{ use wgt::{
error::{ErrorType, WebGpuError}, error::{ErrorType, WebGpuError},
BufferAddress, BufferSize, BufferSizeOrZero, BufferUsages, Color, DynamicOffset, IndexFormat, BufferAddress, BufferSize, BufferUsages, Color, DynamicOffset, IndexFormat, ShaderStages,
ShaderStages, TextureSelector, TextureUsages, TextureViewDimension, VertexStepMode, TextureSelector, TextureUsages, TextureViewDimension, VertexStepMode,
}; };
use crate::command::{ use crate::command::{
@ -362,17 +357,13 @@ struct IndexState {
} }
impl IndexState { impl IndexState {
fn update_buffer<B: hal::DynBuffer + ?Sized>( fn update_buffer(&mut self, range: Range<BufferAddress>, format: IndexFormat) {
&mut self,
binding: &hal::BufferBinding<'_, B>,
format: IndexFormat,
) {
self.buffer_format = Some(format); self.buffer_format = Some(format);
let shift = match format { let shift = match format {
IndexFormat::Uint16 => 1, IndexFormat::Uint16 => 1,
IndexFormat::Uint32 => 2, IndexFormat::Uint32 => 2,
}; };
self.limit = binding.size.get() >> shift; self.limit = (range.end - range.start) >> shift;
} }
fn reset(&mut self) { fn reset(&mut self) {
@ -2339,7 +2330,7 @@ fn set_index_buffer(
buffer: Arc<crate::resource::Buffer>, buffer: Arc<crate::resource::Buffer>,
index_format: IndexFormat, index_format: IndexFormat,
offset: u64, offset: u64,
size: Option<BufferSizeOrZero>, size: Option<BufferSize>,
) -> Result<(), RenderPassErrorInner> { ) -> Result<(), RenderPassErrorInner> {
api_log!("RenderPass::set_index_buffer {}", buffer.error_ident()); api_log!("RenderPass::set_index_buffer {}", buffer.error_ident());
@ -2353,15 +2344,16 @@ fn set_index_buffer(
buffer.check_usage(BufferUsages::INDEX)?; buffer.check_usage(BufferUsages::INDEX)?;
let binding = buffer let (binding, resolved_size) = buffer
.binding(offset, size, state.general.snatch_guard) .binding(offset, size, state.general.snatch_guard)
.map_err(RenderCommandError::from)?; .map_err(RenderCommandError::from)?;
state.index.update_buffer(&binding, index_format); let end = offset + resolved_size;
state.index.update_buffer(offset..end, index_format);
state.general.buffer_memory_init_actions.extend( state.general.buffer_memory_init_actions.extend(
buffer.initialization_status.read().create_action( buffer.initialization_status.read().create_action(
&buffer, &buffer,
offset..(offset + binding.size.get()), offset..end,
MemoryInitKind::NeedsInitializedMemory, MemoryInitKind::NeedsInitializedMemory,
), ),
); );
@ -2379,7 +2371,7 @@ fn set_vertex_buffer(
slot: u32, slot: u32,
buffer: Arc<crate::resource::Buffer>, buffer: Arc<crate::resource::Buffer>,
offset: u64, offset: u64,
size: Option<BufferSizeOrZero>, size: Option<BufferSize>,
) -> Result<(), RenderPassErrorInner> { ) -> Result<(), RenderPassErrorInner> {
api_log!( api_log!(
"RenderPass::set_vertex_buffer {slot} {}", "RenderPass::set_vertex_buffer {slot} {}",
@ -2405,15 +2397,15 @@ fn set_vertex_buffer(
buffer.check_usage(BufferUsages::VERTEX)?; buffer.check_usage(BufferUsages::VERTEX)?;
let binding = buffer let (binding, buffer_size) = buffer
.binding(offset, size, state.general.snatch_guard) .binding(offset, size, state.general.snatch_guard)
.map_err(RenderCommandError::from)?; .map_err(RenderCommandError::from)?;
state.vertex.buffer_sizes[slot as usize] = Some(binding.size.get()); state.vertex.buffer_sizes[slot as usize] = Some(buffer_size);
state.general.buffer_memory_init_actions.extend( state.general.buffer_memory_init_actions.extend(
buffer.initialization_status.read().create_action( buffer.initialization_status.read().create_action(
&buffer, &buffer,
offset..(offset + binding.size.get()), offset..(offset + buffer_size),
MemoryInitKind::NeedsInitializedMemory, MemoryInitKind::NeedsInitializedMemory,
), ),
); );
@ -3090,7 +3082,7 @@ impl Global {
buffer: pass_try!(base, scope, self.resolve_render_pass_buffer_id(buffer_id)), buffer: pass_try!(base, scope, self.resolve_render_pass_buffer_id(buffer_id)),
index_format, index_format,
offset, offset,
size: size.map(NonZeroU64::get), size,
}); });
Ok(()) Ok(())
@ -3111,7 +3103,7 @@ impl Global {
slot, slot,
buffer: pass_try!(base, scope, self.resolve_render_pass_buffer_id(buffer_id)), buffer: pass_try!(base, scope, self.resolve_render_pass_buffer_id(buffer_id)),
offset, offset,
size: size.map(NonZeroU64::get), size,
}); });
Ok(()) Ok(())

View File

@ -1,6 +1,6 @@
use alloc::sync::Arc; use alloc::sync::Arc;
use wgt::{BufferAddress, BufferSizeOrZero, Color}; use wgt::{BufferAddress, BufferSize, Color};
use super::{Rect, RenderBundle}; use super::{Rect, RenderBundle};
use crate::{ use crate::{
@ -24,13 +24,13 @@ pub enum RenderCommand {
buffer_id: id::BufferId, buffer_id: id::BufferId,
index_format: wgt::IndexFormat, index_format: wgt::IndexFormat,
offset: BufferAddress, offset: BufferAddress,
size: Option<BufferSizeOrZero>, size: Option<BufferSize>,
}, },
SetVertexBuffer { SetVertexBuffer {
slot: u32, slot: u32,
buffer_id: id::BufferId, buffer_id: id::BufferId,
offset: BufferAddress, offset: BufferAddress,
size: Option<BufferSizeOrZero>, size: Option<BufferSize>,
}, },
SetBlendConstant(Color), SetBlendConstant(Color),
SetStencilReference(u32), SetStencilReference(u32),
@ -416,20 +416,13 @@ pub enum ArcRenderCommand {
buffer: Arc<Buffer>, buffer: Arc<Buffer>,
index_format: wgt::IndexFormat, index_format: wgt::IndexFormat,
offset: BufferAddress, offset: BufferAddress,
size: Option<BufferSize>,
// For a render pass, this reflects the argument passed by the
// application, which may be `None`. For a finished render bundle, this
// reflects the validated size of the binding, and will be populated
// even in the case that the application omitted the size.
size: Option<BufferSizeOrZero>,
}, },
SetVertexBuffer { SetVertexBuffer {
slot: u32, slot: u32,
buffer: Arc<Buffer>, buffer: Arc<Buffer>,
offset: BufferAddress, offset: BufferAddress,
size: Option<BufferSize>,
// See comment in `SetIndexBuffer`.
size: Option<BufferSizeOrZero>,
}, },
SetBlendConstant(Color), SetBlendConstant(Color),
SetStencilReference(u32), SetStencilReference(u32),

View File

@ -8,7 +8,7 @@ use alloc::{
use core::{ use core::{
fmt, fmt,
mem::{self, ManuallyDrop}, mem::{self, ManuallyDrop},
num::{NonZeroU32, NonZeroU64}, num::NonZeroU32,
sync::atomic::{AtomicBool, Ordering}, sync::atomic::{AtomicBool, Ordering},
}; };
use hal::ShouldBeNonZeroExt; use hal::ShouldBeNonZeroExt;
@ -2198,8 +2198,8 @@ impl Device {
buffer.check_usage(pub_usage)?; buffer.check_usage(pub_usage)?;
let bb = buffer.binding(bb.offset, bb.size.map(NonZeroU64::get), snatch_guard)?; let (bb, bind_size) = buffer.binding(bb.offset, bb.size, snatch_guard)?;
let bind_size = bb.size.get(); let bind_end = bb.offset + bind_size;
if bind_size > range_limit as u64 { if bind_size > range_limit as u64 {
return Err(Error::BufferRangeTooLarge { return Err(Error::BufferRangeTooLarge {
@ -2214,8 +2214,8 @@ impl Device {
dynamic_binding_info.push(binding_model::BindGroupDynamicBindingData { dynamic_binding_info.push(binding_model::BindGroupDynamicBindingData {
binding_idx: binding, binding_idx: binding,
buffer_size: buffer.size, buffer_size: buffer.size,
binding_range: bb.offset..bb.offset + bind_size, binding_range: bb.offset..bind_end,
maximum_dynamic_offset: buffer.size - bb.offset - bind_size, maximum_dynamic_offset: buffer.size - bind_end,
binding_type: binding_ty, binding_type: binding_ty,
}); });
} }

View File

@ -234,7 +234,7 @@ impl Dispatch {
}], }],
buffers: &[unsafe { buffers: &[unsafe {
// SAFETY: We just created the buffer with this size. // SAFETY: We just created the buffer with this size.
hal::BufferBinding::new_unchecked(dst_buffer.as_ref(), 0, DST_BUFFER_SIZE) hal::BufferBinding::new_unchecked(dst_buffer.as_ref(), 0, Some(DST_BUFFER_SIZE))
}], }],
samplers: &[], samplers: &[],
textures: &[], textures: &[],

View File

@ -508,17 +508,17 @@ impl Buffer {
pub fn resolve_binding_size( pub fn resolve_binding_size(
&self, &self,
offset: wgt::BufferAddress, offset: wgt::BufferAddress,
binding_size: Option<wgt::BufferSizeOrZero>, binding_size: Option<wgt::BufferSize>,
) -> Result<wgt::BufferSizeOrZero, BindingError> { ) -> Result<u64, BindingError> {
let buffer_size = self.size; let buffer_size = self.size;
match binding_size { match binding_size {
Some(binding_size) => match offset.checked_add(binding_size) { Some(binding_size) => match offset.checked_add(binding_size.get()) {
Some(end) if end <= buffer_size => Ok(binding_size), Some(end) if end <= buffer_size => Ok(binding_size.get()),
_ => Err(BindingError::BindingRangeTooLarge { _ => Err(BindingError::BindingRangeTooLarge {
buffer: self.error_ident(), buffer: self.error_ident(),
offset, offset,
binding_size, binding_size: binding_size.get(),
buffer_size, buffer_size,
}), }),
}, },
@ -535,35 +535,38 @@ impl Buffer {
} }
/// Create a new [`hal::BufferBinding`] for the buffer with `offset` and /// Create a new [`hal::BufferBinding`] for the buffer with `offset` and
/// `size`. /// `binding_size`.
/// ///
/// If `size` is `None`, then the remainder of the buffer starting from /// If `binding_size` is `None`, then the remainder of the buffer starting
/// `offset` is used. /// from `offset` is used.
/// ///
/// If the binding would overflow the buffer, then an error is returned. /// If the binding would overflow the buffer, then an error is returned.
/// ///
/// Zero-size bindings are permitted here for historical reasons. Although /// A zero-size binding at the end of the buffer is permitted here for historical reasons. Although
/// zero-size bindings are permitted by WebGPU, they are not permitted by /// zero-size bindings are permitted by WebGPU, they are not permitted by
/// some backends. Previous documentation for `hal::BufferBinding` /// some backends. The zero-size binding need to be quashed or remapped to a
/// disallowed zero-size bindings, but this restriction was not honored /// non-zero size, either universally in wgpu-core, or in specific backends
/// elsewhere in the code. Zero-size bindings need to be quashed or remapped /// that do not support them. See
/// to a non-zero size, either universally in wgpu-core, or in specific
/// backends that do not support them. See
/// [#3170](https://github.com/gfx-rs/wgpu/issues/3170). /// [#3170](https://github.com/gfx-rs/wgpu/issues/3170).
///
/// Although it seems like it would be simpler and safer to use the resolved
/// size in the returned [`hal::BufferBinding`], doing this (and removing
/// redundant logic in backends to resolve the implicit size) was observed
/// to cause problems in certain CTS tests, so an implicit size
/// specification is preserved in the output.
pub fn binding<'a>( pub fn binding<'a>(
&'a self, &'a self,
offset: wgt::BufferAddress, offset: wgt::BufferAddress,
binding_size: Option<wgt::BufferSizeOrZero>, binding_size: Option<wgt::BufferSize>,
snatch_guard: &'a SnatchGuard, snatch_guard: &'a SnatchGuard,
) -> Result<hal::BufferBinding<'a, dyn hal::DynBuffer>, BindingError> { ) -> Result<(hal::BufferBinding<'a, dyn hal::DynBuffer>, u64), BindingError> {
let buf_raw = self.try_raw(snatch_guard)?; let buf_raw = self.try_raw(snatch_guard)?;
let resolved_size = self.resolve_binding_size(offset, binding_size)?; let resolved_size = self.resolve_binding_size(offset, binding_size)?;
unsafe { unsafe {
// SAFETY: The offset and size passed to hal::BufferBinding::new_unchecked must // SAFETY: The offset and size passed to hal::BufferBinding::new_unchecked must
// define a binding contained within the buffer. // define a binding contained within the buffer.
Ok(hal::BufferBinding::new_unchecked( Ok((
buf_raw, hal::BufferBinding::new_unchecked(buf_raw, offset, binding_size),
offset,
resolved_size, resolved_size,
)) ))
} }

View File

@ -14,7 +14,9 @@ use winit::{
use std::{ use std::{
borrow::{Borrow, Cow}, borrow::{Borrow, Cow},
iter, ptr, iter,
num::NonZeroU64,
ptr,
time::Instant, time::Instant,
}; };
@ -447,7 +449,11 @@ impl<A: hal::Api> Example<A> {
let global_group = { let global_group = {
let global_buffer_binding = unsafe { let global_buffer_binding = unsafe {
// SAFETY: This is the same size that was specified for buffer creation. // SAFETY: This is the same size that was specified for buffer creation.
hal::BufferBinding::new_unchecked(&global_buffer, 0, global_buffer_desc.size) hal::BufferBinding::new_unchecked(
&global_buffer,
0,
NonZeroU64::new(global_buffer_desc.size),
)
}; };
let texture_binding = hal::TextureBinding { let texture_binding = hal::TextureBinding {
view: &texture_view, view: &texture_view,
@ -487,7 +493,7 @@ impl<A: hal::Api> Example<A> {
hal::BufferBinding::new_unchecked( hal::BufferBinding::new_unchecked(
&local_buffer, &local_buffer,
0, 0,
wgpu_types::BufferSize::new(size_of::<Locals>() as _).unwrap(), wgpu_types::BufferSize::new(size_of::<Locals>() as _),
) )
}; };
let local_group_desc = hal::BindGroupDescriptor { let local_group_desc = hal::BindGroupDescriptor {

View File

@ -1136,7 +1136,7 @@ impl crate::CommandEncoder for super::CommandEncoder {
) { ) {
let ibv = Direct3D12::D3D12_INDEX_BUFFER_VIEW { let ibv = Direct3D12::D3D12_INDEX_BUFFER_VIEW {
BufferLocation: binding.resolve_address(), BufferLocation: binding.resolve_address(),
SizeInBytes: binding.size.try_into().unwrap(), SizeInBytes: binding.resolve_size().try_into().unwrap(),
Format: auxil::dxgi::conv::map_index_format(format), Format: auxil::dxgi::conv::map_index_format(format),
}; };
@ -1149,7 +1149,7 @@ impl crate::CommandEncoder for super::CommandEncoder {
) { ) {
let vb = &mut self.pass.vertex_buffers[index as usize]; let vb = &mut self.pass.vertex_buffers[index as usize];
vb.BufferLocation = binding.resolve_address(); vb.BufferLocation = binding.resolve_address();
vb.SizeInBytes = binding.size.try_into().unwrap(); vb.SizeInBytes = binding.resolve_size().try_into().unwrap();
self.pass.dirty_vertex_buffers |= 1 << index; self.pass.dirty_vertex_buffers |= 1 << index;
} }

View File

@ -1442,7 +1442,7 @@ impl crate::Device for super::Device {
let end = start + entry.count as usize; let end = start + entry.count as usize;
for data in &desc.buffers[start..end] { for data in &desc.buffers[start..end] {
let gpu_address = data.resolve_address(); let gpu_address = data.resolve_address();
let mut size = data.size.try_into().unwrap(); let mut size = data.resolve_size().try_into().unwrap();
if has_dynamic_offset { if has_dynamic_offset {
match ty { match ty {

View File

@ -865,6 +865,13 @@ unsafe impl Sync for Buffer {}
impl crate::DynBuffer for Buffer {} impl crate::DynBuffer for Buffer {}
impl crate::BufferBinding<'_, Buffer> { impl crate::BufferBinding<'_, Buffer> {
fn resolve_size(&self) -> wgt::BufferAddress {
match self.size {
Some(size) => size.get(),
None => self.buffer.size - self.offset,
}
}
// TODO: Return GPU handle directly? // TODO: Return GPU handle directly?
fn resolve_address(&self) -> wgt::BufferAddress { fn resolve_address(&self) -> wgt::BufferAddress {
(unsafe { self.buffer.resource.GetGPUVirtualAddress() }) + self.offset (unsafe { self.buffer.resource.GetGPUVirtualAddress() }) + self.offset

View File

@ -534,6 +534,7 @@ impl crate::Device for super::Device {
return Ok(super::Buffer { return Ok(super::Buffer {
raw: None, raw: None,
target, target,
size: desc.size,
map_flags: 0, map_flags: 0,
data: Some(Arc::new(MaybeMutex::new(vec![0; desc.size as usize]))), data: Some(Arc::new(MaybeMutex::new(vec![0; desc.size as usize]))),
offset_of_current_mapping: Arc::new(MaybeMutex::new(0)), offset_of_current_mapping: Arc::new(MaybeMutex::new(0)),
@ -633,6 +634,7 @@ impl crate::Device for super::Device {
Ok(super::Buffer { Ok(super::Buffer {
raw, raw,
target, target,
size: desc.size,
map_flags, map_flags,
data, data,
offset_of_current_mapping: Arc::new(MaybeMutex::new(0)), offset_of_current_mapping: Arc::new(MaybeMutex::new(0)),
@ -1261,11 +1263,13 @@ impl crate::Device for super::Device {
let binding = match layout.ty { let binding = match layout.ty {
wgt::BindingType::Buffer { .. } => { wgt::BindingType::Buffer { .. } => {
let bb = &desc.buffers[entry.resource_index as usize]; let bb = &desc.buffers[entry.resource_index as usize];
assert!(bb.size != 0, "zero-size bindings are not supported");
super::RawBinding::Buffer { super::RawBinding::Buffer {
raw: bb.buffer.raw.unwrap(), raw: bb.buffer.raw.unwrap(),
offset: bb.offset.try_into().unwrap(), offset: bb.offset as i32,
size: bb.size.try_into().unwrap(), size: match bb.size {
Some(s) => s.get() as i32,
None => (bb.buffer.size - bb.offset) as i32,
},
} }
} }
wgt::BindingType::Sampler { .. } => { wgt::BindingType::Sampler { .. } => {

View File

@ -342,6 +342,7 @@ impl Drop for Queue {
pub struct Buffer { pub struct Buffer {
raw: Option<glow::Buffer>, raw: Option<glow::Buffer>,
target: BindTarget, target: BindTarget,
size: wgt::BufferAddress,
map_flags: u32, map_flags: u32,
data: Option<Arc<MaybeMutex<Vec<u8>>>>, data: Option<Arc<MaybeMutex<Vec<u8>>>>,
offset_of_current_mapping: Arc<MaybeMutex<wgt::BufferAddress>>, offset_of_current_mapping: Arc<MaybeMutex<wgt::BufferAddress>>,

View File

@ -1972,8 +1972,8 @@ pub struct PipelineLayoutDescriptor<'a, B: DynBindGroupLayout + ?Sized> {
/// ///
/// The recommended way to construct a `BufferBinding` is using the `binding` /// The recommended way to construct a `BufferBinding` is using the `binding`
/// method on a wgpu-core `Buffer`, which will validate the binding size /// method on a wgpu-core `Buffer`, which will validate the binding size
/// against the buffer size. An unsafe `new_unchecked` constructor is also /// against the buffer size. A `new_unchecked` constructor is also provided for
/// provided for cases where direct construction is necessary. /// cases where direct construction is necessary.
/// ///
/// ## Accessible region /// ## Accessible region
/// ///
@ -2035,7 +2035,11 @@ pub struct BufferBinding<'a, B: DynBuffer + ?Sized> {
pub offset: wgt::BufferAddress, pub offset: wgt::BufferAddress,
/// The size of the region bound, in bytes. /// The size of the region bound, in bytes.
pub size: wgt::BufferSizeOrZero, ///
/// If `None`, the region extends from `offset` to the end of the
/// buffer. Given the restrictions on `offset`, this means that
/// the size is always greater than zero.
pub size: Option<wgt::BufferSize>,
} }
// We must implement this manually because `B` is not necessarily `Clone`. // We must implement this manually because `B` is not necessarily `Clone`.
@ -2068,6 +2072,15 @@ impl ShouldBeNonZeroExt for u64 {
} }
} }
impl ShouldBeNonZeroExt for Option<NonZeroU64> {
fn get(&self) -> u64 {
match *self {
Some(non_zero) => non_zero.get(),
None => 0,
}
}
}
impl<'a, B: DynBuffer + ?Sized> BufferBinding<'a, B> { impl<'a, B: DynBuffer + ?Sized> BufferBinding<'a, B> {
/// Construct a `BufferBinding` with the given contents. /// Construct a `BufferBinding` with the given contents.
/// ///
@ -2082,10 +2095,10 @@ impl<'a, B: DynBuffer + ?Sized> BufferBinding<'a, B> {
/// bytes starting at `offset` is contained within the buffer. /// bytes starting at `offset` is contained within the buffer.
/// ///
/// The `S` type parameter is a temporary convenience to allow callers to /// The `S` type parameter is a temporary convenience to allow callers to
/// pass either a `u64` or a `NonZeroU64`. When the zero-size binding issue /// pass a zero size. When the zero-size binding issue is resolved, the
/// is resolved, the argument should just match the type of the member. /// argument should just match the type of the member.
/// TODO(<https://github.com/gfx-rs/wgpu/issues/3170>): remove the parameter /// TODO(<https://github.com/gfx-rs/wgpu/issues/3170>): remove the parameter
pub unsafe fn new_unchecked<S: Into<wgt::BufferSizeOrZero>>( pub unsafe fn new_unchecked<S: Into<Option<NonZeroU64>>>(
buffer: &'a B, buffer: &'a B,
offset: wgt::BufferAddress, offset: wgt::BufferAddress,
size: S, size: S,

View File

@ -4,7 +4,7 @@ use alloc::{
borrow::{Cow, ToOwned as _}, borrow::{Cow, ToOwned as _},
vec::Vec, vec::Vec,
}; };
use core::{num::NonZeroU64, ops::Range}; use core::ops::Range;
use metal::{ use metal::{
MTLIndexType, MTLLoadAction, MTLPrimitiveType, MTLScissorRect, MTLSize, MTLStoreAction, MTLIndexType, MTLLoadAction, MTLPrimitiveType, MTLScissorRect, MTLSize, MTLStoreAction,
MTLViewport, MTLVisibilityResultMode, NSRange, MTLViewport, MTLVisibilityResultMode, NSRange,
@ -977,11 +977,15 @@ impl crate::CommandEncoder for super::CommandEncoder {
let encoder = self.state.render.as_ref().unwrap(); let encoder = self.state.render.as_ref().unwrap();
encoder.set_vertex_buffer(buffer_index, Some(&binding.buffer.raw), binding.offset); encoder.set_vertex_buffer(buffer_index, Some(&binding.buffer.raw), binding.offset);
// https://github.com/gfx-rs/wgpu/issues/3170 let buffer_size = binding.resolve_size();
let size = if buffer_size > 0 {
NonZeroU64::new(binding.size).expect("zero-size vertex buffers are not supported"); self.state.vertex_buffer_size_map.insert(
buffer_index,
self.state.vertex_buffer_size_map.insert(buffer_index, size); core::num::NonZeroU64::new(buffer_size).unwrap(),
);
} else {
self.state.vertex_buffer_size_map.remove(&buffer_index);
}
if let Some((index, sizes)) = self if let Some((index, sizes)) = self
.state .state

View File

@ -1,5 +1,5 @@
use alloc::{borrow::ToOwned as _, sync::Arc, vec::Vec}; use alloc::{borrow::ToOwned as _, sync::Arc, vec::Vec};
use core::{num::NonZeroU64, ptr::NonNull, sync::atomic}; use core::{ptr::NonNull, sync::atomic};
use std::{thread, time}; use std::{thread, time};
use parking_lot::Mutex; use parking_lot::Mutex;
@ -340,6 +340,10 @@ impl super::Device {
} }
} }
pub unsafe fn buffer_from_raw(raw: metal::Buffer, size: wgt::BufferAddress) -> super::Buffer {
super::Buffer { raw, size }
}
pub fn raw_device(&self) -> &Mutex<metal::Device> { pub fn raw_device(&self) -> &Mutex<metal::Device> {
&self.shared.device &self.shared.device
} }
@ -369,7 +373,10 @@ impl crate::Device for super::Device {
raw.set_label(label); raw.set_label(label);
} }
self.counters.buffers.add(1); self.counters.buffers.add(1);
Ok(super::Buffer { raw }) Ok(super::Buffer {
raw,
size: desc.size,
})
}) })
} }
unsafe fn destroy_buffer(&self, _buffer: super::Buffer) { unsafe fn destroy_buffer(&self, _buffer: super::Buffer) {
@ -928,12 +935,14 @@ impl crate::Device for super::Device {
let end = start + 1; let end = start + 1;
bg.buffers bg.buffers
.extend(desc.buffers[start..end].iter().map(|source| { .extend(desc.buffers[start..end].iter().map(|source| {
// https://github.com/gfx-rs/wgpu/issues/3170 // Given the restrictions on `BufferBinding::offset`,
let source_size = NonZeroU64::new(source.size) // this should never be `None`.
.expect("zero-size bindings are not supported"); let remaining_size = wgt::BufferSize::new(
source.buffer.size - source.offset,
);
let binding_size = match ty { let binding_size = match ty {
wgt::BufferBindingType::Storage { .. } => { wgt::BufferBindingType::Storage { .. } => {
Some(source_size) source.size.or(remaining_size)
} }
_ => None, _ => None,
}; };

View File

@ -502,6 +502,7 @@ impl crate::Queue for Queue {
#[derive(Debug)] #[derive(Debug)]
pub struct Buffer { pub struct Buffer {
raw: metal::Buffer, raw: metal::Buffer,
size: wgt::BufferAddress,
} }
unsafe impl Send for Buffer {} unsafe impl Send for Buffer {}
@ -515,6 +516,15 @@ impl Buffer {
} }
} }
impl crate::BufferBinding<'_, Buffer> {
fn resolve_size(&self) -> wgt::BufferAddress {
match self.size {
Some(size) => size.get(),
None => self.buffer.size - self.offset,
}
}
}
#[derive(Debug)] #[derive(Debug)]
pub struct Texture { pub struct Texture {
raw: metal::Texture, raw: metal::Texture,

View File

@ -1804,12 +1804,12 @@ impl crate::Device for super::Device {
(buffer_infos, local_buffer_infos) = (buffer_infos, local_buffer_infos) =
buffer_infos.extend(desc.buffers[start as usize..end as usize].iter().map( buffer_infos.extend(desc.buffers[start as usize..end as usize].iter().map(
|binding| { |binding| {
// https://github.com/gfx-rs/wgpu/issues/3170
assert!(binding.size != 0, "zero-size bindings are not supported");
vk::DescriptorBufferInfo::default() vk::DescriptorBufferInfo::default()
.buffer(binding.buffer.raw) .buffer(binding.buffer.raw)
.offset(binding.offset) .offset(binding.offset)
.range(binding.size) .range(
binding.size.map_or(vk::WHOLE_SIZE, wgt::BufferSize::get),
)
}, },
)); ));
write.buffer_info(local_buffer_infos) write.buffer_info(local_buffer_infos)

View File

@ -61,12 +61,6 @@ pub type BufferAddress = u64;
/// [`BufferSlice`]: ../wgpu/struct.BufferSlice.html /// [`BufferSlice`]: ../wgpu/struct.BufferSlice.html
pub type BufferSize = core::num::NonZeroU64; pub type BufferSize = core::num::NonZeroU64;
/// Integral type used for buffer sizes that may be zero.
///
/// Although the wgpu Rust API disallows zero-size `BufferSlice` and wgpu-hal
/// disallows zero-size bindings, WebGPU permits zero-size buffers and bindings.
pub type BufferSizeOrZero = u64;
/// Integral type used for binding locations in shaders. /// Integral type used for binding locations in shaders.
/// ///
/// Used in [`VertexAttribute`]s and errors. /// Used in [`VertexAttribute`]s and errors.