Restore unintentional support for zero-size buffers

This commit is contained in:
Andy Leiserson 2025-07-02 14:16:02 -07:00 committed by Jim Blandy
parent ef428fcab8
commit c0a580d6f0
16 changed files with 129 additions and 101 deletions

View File

@ -106,7 +106,7 @@ pub enum BindingError {
binding_size: u64, binding_size: u64,
buffer_size: u64, buffer_size: u64,
}, },
#[error("Buffer {buffer}: Binding offset {offset} is greater than or equal to buffer size {buffer_size}")] #[error("Buffer {buffer}: Binding offset {offset} is greater than buffer size {buffer_size}")]
BindingOffsetTooLarge { BindingOffsetTooLarge {
buffer: ResourceErrorIdent, buffer: ResourceErrorIdent,
offset: wgt::BufferAddress, offset: wgt::BufferAddress,

View File

@ -93,6 +93,7 @@ use core::{
use arrayvec::ArrayVec; use arrayvec::ArrayVec;
use thiserror::Error; use thiserror::Error;
use wgpu_hal::ShouldBeNonZeroExt;
use wgt::error::{ErrorType, WebGpuError}; use wgt::error::{ErrorType, WebGpuError};
use crate::{ use crate::{
@ -504,7 +505,7 @@ impl RenderBundleEncoder {
buffer_id, buffer_id,
index_format, index_format,
offset, offset,
size, size: size.map(NonZeroU64::get),
}); });
} }
} }
@ -609,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<NonZeroU64>, size: Option<wgt::BufferSizeOrZero>,
) -> Result<(), RenderBundleErrorInner> { ) -> Result<(), RenderBundleErrorInner> {
let buffer = buffer_guard.get(buffer_id).get()?; let buffer = buffer_guard.get(buffer_id).get()?;
@ -641,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<NonZeroU64>, size: Option<wgt::BufferSizeOrZero>,
) -> 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 {
@ -1166,11 +1167,8 @@ impl IndexState {
.range .range
.end .end
.checked_sub(self.range.start) .checked_sub(self.range.start)
.and_then(wgt::BufferSize::new); .filter(|_| self.range.end <= self.buffer.size)
assert!( .expect("index range must be contained in buffer");
self.range.end <= self.buffer.size && binding_size.is_some(),
"index buffer range must have non-zero size and be contained in buffer",
);
if self.is_dirty { if self.is_dirty {
self.is_dirty = false; self.is_dirty = false;
@ -1178,7 +1176,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: binding_size, size: Some(binding_size),
}) })
} else { } else {
None None
@ -1221,16 +1219,12 @@ impl VertexState {
/// ///
/// `slot` is the index of the vertex buffer slot that `self` tracks. /// `slot` is the index of the vertex buffer slot that `self` tracks.
fn flush(&mut self, slot: u32) -> Option<ArcRenderCommand> { fn flush(&mut self, slot: u32) -> Option<ArcRenderCommand> {
// This was all checked before, but let's check again just in case.
let binding_size = self let binding_size = self
.range .range
.end .end
.checked_sub(self.range.start) .checked_sub(self.range.start)
.and_then(wgt::BufferSize::new); .filter(|_| self.range.end <= self.buffer.size)
assert!( .expect("vertex range must be contained in buffer");
self.range.end <= self.buffer.size && binding_size.is_some(),
"vertex buffer range must have non-zero size and be contained in buffer",
);
if self.is_dirty { if self.is_dirty {
self.is_dirty = false; self.is_dirty = false;
@ -1238,7 +1232,7 @@ impl VertexState {
slot, slot,
buffer: self.buffer.clone(), buffer: self.buffer.clone(),
offset: self.range.start, offset: self.range.start,
size: binding_size, size: Some(binding_size),
}) })
} else { } else {
None None
@ -1602,7 +1596,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, slice}; use core::{convert::TryInto, num::NonZeroU64, slice};
use wgt::{BufferAddress, BufferSize, DynamicOffset, IndexFormat}; use wgt::{BufferAddress, BufferSize, DynamicOffset, IndexFormat};
/// # Safety /// # Safety
@ -1661,7 +1655,7 @@ pub mod bundle_ffi {
slot, slot,
buffer_id, buffer_id,
offset, offset,
size, size: size.map(NonZeroU64::get),
}); });
} }

View File

@ -1,12 +1,17 @@
use alloc::{borrow::Cow, sync::Arc, vec::Vec}; use alloc::{borrow::Cow, sync::Arc, vec::Vec};
use core::{fmt, num::NonZeroU32, str}; use core::{
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, BufferUsages, Color, DynamicOffset, IndexFormat, ShaderStages, BufferAddress, BufferSize, BufferSizeOrZero, BufferUsages, Color, DynamicOffset, IndexFormat,
TextureSelector, TextureUsages, TextureViewDimension, VertexStepMode, ShaderStages, TextureSelector, TextureUsages, TextureViewDimension, VertexStepMode,
}; };
use crate::command::{ use crate::command::{
@ -2333,7 +2338,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<BufferSize>, size: Option<BufferSizeOrZero>,
) -> Result<(), RenderPassErrorInner> { ) -> Result<(), RenderPassErrorInner> {
api_log!("RenderPass::set_index_buffer {}", buffer.error_ident()); api_log!("RenderPass::set_index_buffer {}", buffer.error_ident());
@ -2373,7 +2378,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<BufferSize>, size: Option<BufferSizeOrZero>,
) -> Result<(), RenderPassErrorInner> { ) -> Result<(), RenderPassErrorInner> {
api_log!( api_log!(
"RenderPass::set_vertex_buffer {slot} {}", "RenderPass::set_vertex_buffer {slot} {}",
@ -3084,7 +3089,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: size.map(NonZeroU64::get),
}); });
Ok(()) Ok(())
@ -3105,7 +3110,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: size.map(NonZeroU64::get),
}); });
Ok(()) Ok(())

View File

@ -1,6 +1,6 @@
use alloc::sync::Arc; use alloc::sync::Arc;
use wgt::{BufferAddress, BufferSize, Color}; use wgt::{BufferAddress, BufferSizeOrZero, 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<BufferSize>, size: Option<BufferSizeOrZero>,
}, },
SetVertexBuffer { SetVertexBuffer {
slot: u32, slot: u32,
buffer_id: id::BufferId, buffer_id: id::BufferId,
offset: BufferAddress, offset: BufferAddress,
size: Option<BufferSize>, size: Option<BufferSizeOrZero>,
}, },
SetBlendConstant(Color), SetBlendConstant(Color),
SetStencilReference(u32), SetStencilReference(u32),
@ -418,21 +418,18 @@ pub enum ArcRenderCommand {
offset: BufferAddress, offset: BufferAddress,
// For a render pass, this reflects the argument passed by the // For a render pass, this reflects the argument passed by the
// application, which may be `None`. For a render bundle, this reflects // application, which may be `None`. For a finished render bundle, this
// the validated size of the binding, and will be populated even in the // reflects the validated size of the binding, and will be populated
// case that the application omitted the size. // even in the case that the application omitted the size.
size: Option<BufferSize>, size: Option<BufferSizeOrZero>,
}, },
SetVertexBuffer { SetVertexBuffer {
slot: u32, slot: u32,
buffer: Arc<Buffer>, buffer: Arc<Buffer>,
offset: BufferAddress, offset: BufferAddress,
// For a render pass, this reflects the argument passed by the // See comment in `SetIndexBuffer`.
// application, which may be `None`. For a render bundle, this reflects size: Option<BufferSizeOrZero>,
// the validated size of the binding, and will be populated even in the
// case that the application omitted the size.
size: Option<BufferSize>,
}, },
SetBlendConstant(Color), SetBlendConstant(Color),
SetStencilReference(u32), SetStencilReference(u32),

View File

@ -8,9 +8,10 @@ use alloc::{
use core::{ use core::{
fmt, fmt,
mem::{self, ManuallyDrop}, mem::{self, ManuallyDrop},
num::NonZeroU32, num::{NonZeroU32, NonZeroU64},
sync::atomic::{AtomicBool, Ordering}, sync::atomic::{AtomicBool, Ordering},
}; };
use hal::ShouldBeNonZeroExt;
use arrayvec::ArrayVec; use arrayvec::ArrayVec;
use bitflags::Flags; use bitflags::Flags;
@ -2197,7 +2198,7 @@ impl Device {
buffer.check_usage(pub_usage)?; buffer.check_usage(pub_usage)?;
let bb = buffer.binding(bb.offset, bb.size, snatch_guard)?; let bb = buffer.binding(bb.offset, bb.size.map(NonZeroU64::get), snatch_guard)?;
let bind_size = bb.size.get(); let bind_size = bb.size.get();
if bind_size > range_limit as u64 { if bind_size > range_limit as u64 {

View File

@ -490,35 +490,32 @@ impl Buffer {
/// If `size` is `None`, then the remainder of the buffer starting from /// If `size` is `None`, then the remainder of the buffer starting from
/// `offset` is used. /// `offset` is used.
/// ///
/// If the binding would overflow the buffer or is empty (see /// If the binding would overflow the buffer, then an error is returned.
/// [`hal::BufferBinding`]), then an error is returned. ///
/// Zero-size bindings are permitted here for historical reasons. Although
/// zero-size bindings are permitted by WebGPU, they are not permitted by
/// some backends. See [`Buffer::binding`] and
/// [#3170](https://github.com/gfx-rs/wgpu/issues/3170).
pub fn resolve_binding_size( pub fn resolve_binding_size(
&self, &self,
offset: wgt::BufferAddress, offset: wgt::BufferAddress,
binding_size: Option<wgt::BufferSize>, binding_size: Option<wgt::BufferSizeOrZero>,
) -> Result<wgt::BufferSize, BindingError> { ) -> Result<wgt::BufferSizeOrZero, BindingError> {
let buffer_size = self.size; let buffer_size = self.size;
match binding_size { match binding_size {
Some(binding_size) => { Some(binding_size) => match offset.checked_add(binding_size) {
match offset.checked_add(binding_size.get()) { Some(end) if end <= buffer_size => Ok(binding_size),
// `binding_size` is not zero which means `end == buffer_size` is ok. _ => Err(BindingError::BindingRangeTooLarge {
Some(end) if end <= buffer_size => Ok(binding_size), buffer: self.error_ident(),
_ => Err(BindingError::BindingRangeTooLarge { offset,
buffer: self.error_ident(), binding_size,
offset, buffer_size,
binding_size: binding_size.get(), }),
buffer_size, },
}),
}
}
None => { None => {
// We require that `buffer_size - offset` converts to
// `BufferSize` (`NonZeroU64`) because bindings must not be
// empty.
buffer_size buffer_size
.checked_sub(offset) .checked_sub(offset)
.and_then(wgt::BufferSize::new)
.ok_or_else(|| BindingError::BindingOffsetTooLarge { .ok_or_else(|| BindingError::BindingOffsetTooLarge {
buffer: self.error_ident(), buffer: self.error_ident(),
offset, offset,
@ -534,12 +531,20 @@ impl Buffer {
/// If `size` is `None`, then the remainder of the buffer starting from /// If `size` is `None`, then the remainder of the buffer starting from
/// `offset` is used. /// `offset` is used.
/// ///
/// If the binding would overflow the buffer or is empty (see /// If the binding would overflow the buffer, then an error is returned.
/// [`hal::BufferBinding`]), then an error is returned. ///
/// Zero-size bindings are permitted here for historical reasons. Although
/// zero-size bindings are permitted by WebGPU, they are not permitted by
/// some backends. Previous documentation for `hal::BufferBinding`
/// disallowed zero-size bindings, but this restriction was not honored
/// elsewhere in the code. Zero-size bindings need to be quashed or remapped
/// 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).
pub fn binding<'a>( pub fn binding<'a>(
&'a self, &'a self,
offset: wgt::BufferAddress, offset: wgt::BufferAddress,
binding_size: Option<wgt::BufferSize>, binding_size: Option<wgt::BufferSizeOrZero>,
snatch_guard: &'a SnatchGuard, snatch_guard: &'a SnatchGuard,
) -> Result<hal::BufferBinding<'a, dyn hal::DynBuffer>, BindingError> { ) -> Result<hal::BufferBinding<'a, dyn hal::DynBuffer>, BindingError> {
let buf_raw = self.try_raw(snatch_guard)?; let buf_raw = self.try_raw(snatch_guard)?;

View File

@ -447,11 +447,7 @@ 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( hal::BufferBinding::new_unchecked(&global_buffer, 0, global_buffer_desc.size)
&global_buffer,
0,
global_buffer_desc.size.try_into().unwrap(),
)
}; };
let texture_binding = hal::TextureBinding { let texture_binding = hal::TextureBinding {
view: &texture_view, view: &texture_view,

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.resolve_size() as u32, SizeInBytes: binding.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.resolve_size() as u32; vb.SizeInBytes = binding.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.resolve_size() as u32; let mut size = data.size.try_into().unwrap();
if has_dynamic_offset { if has_dynamic_offset {
match ty { match ty {

View File

@ -865,13 +865,6 @@ 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

@ -1261,10 +1261,11 @@ 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.try_into().unwrap(),
size: bb.size.get().try_into().unwrap(), size: bb.size.try_into().unwrap(),
} }
} }
wgt::BindingType::Sampler { .. } => { wgt::BindingType::Sampler { .. } => {

View File

@ -297,7 +297,7 @@ use core::{
borrow::Borrow, borrow::Borrow,
error::Error, error::Error,
fmt, fmt,
num::NonZeroU32, num::{NonZeroU32, NonZeroU64},
ops::{Range, RangeInclusive}, ops::{Range, RangeInclusive},
ptr::NonNull, ptr::NonNull,
}; };
@ -1979,7 +1979,7 @@ pub struct PipelineLayoutDescriptor<'a, B: DynBindGroupLayout + ?Sized> {
/// ///
/// `wgpu_hal` guarantees that shaders compiled with /// `wgpu_hal` guarantees that shaders compiled with
/// [`ShaderModuleDescriptor::runtime_checks`] set to `true` cannot read or /// [`ShaderModuleDescriptor::runtime_checks`] set to `true` cannot read or
/// write data via this binding outside the *accessible region* of [`buffer`]: /// write data via this binding outside the *accessible region* of a buffer:
/// ///
/// - The accessible region starts at [`offset`]. /// - The accessible region starts at [`offset`].
/// ///
@ -2004,14 +2004,14 @@ pub struct PipelineLayoutDescriptor<'a, B: DynBindGroupLayout + ?Sized> {
/// Some back ends cannot tolerate zero-length regions; for example, see /// Some back ends cannot tolerate zero-length regions; for example, see
/// [VUID-VkDescriptorBufferInfo-offset-00340][340] and /// [VUID-VkDescriptorBufferInfo-offset-00340][340] and
/// [VUID-VkDescriptorBufferInfo-range-00341][341], or the /// [VUID-VkDescriptorBufferInfo-range-00341][341], or the
/// documentation for GLES's [glBindBufferRange][bbr]. For this reason, a valid /// documentation for GLES's [glBindBufferRange][bbr]. This documentation
/// `BufferBinding` must have `offset` strictly less than the size of the /// previously stated that a `BufferBinding` must have `offset` strictly less
/// buffer. /// than the size of the buffer, but this restriction was not honored elsewhere
/// in the code, so has been removed. However, it remains the case that
/// some backends do not support zero-length bindings, so additional
/// logic is needed somewhere to handle this properly. See
/// [#3170](https://github.com/gfx-rs/wgpu/issues/3170).
/// ///
/// WebGPU allows zero-length bindings, and there is not currently a mechanism
/// in place
///
/// [`buffer`]: BufferBinding::buffer
/// [`offset`]: BufferBinding::offset /// [`offset`]: BufferBinding::offset
/// [`size`]: BufferBinding::size /// [`size`]: BufferBinding::size
/// [`Storage`]: wgt::BufferBindingType::Storage /// [`Storage`]: wgt::BufferBindingType::Storage
@ -2031,12 +2031,11 @@ pub struct BufferBinding<'a, B: DynBuffer + ?Sized> {
/// The offset at which the bound region starts. /// The offset at which the bound region starts.
/// ///
/// Because zero-length bindings are not permitted (see above), this must be /// This must be less or equal to the size of the buffer.
/// strictly less than the size of the buffer.
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::BufferSize, pub size: wgt::BufferSizeOrZero,
} }
// We must implement this manually because `B` is not necessarily `Clone`. // We must implement this manually because `B` is not necessarily `Clone`.
@ -2050,6 +2049,25 @@ impl<B: DynBuffer + ?Sized> Clone for BufferBinding<'_, B> {
} }
} }
/// Temporary convenience trait to let us call `.get()` on `u64`s in code that
/// really wants to be using `NonZeroU64`.
/// TODO(<https://github.com/gfx-rs/wgpu/issues/3170>): remove this
pub trait ShouldBeNonZeroExt {
fn get(&self) -> u64;
}
impl ShouldBeNonZeroExt for NonZeroU64 {
fn get(&self) -> u64 {
NonZeroU64::get(*self)
}
}
impl ShouldBeNonZeroExt for u64 {
fn get(&self) -> u64 {
*self
}
}
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.
/// ///
@ -2062,15 +2080,20 @@ impl<'a, B: DynBuffer + ?Sized> BufferBinding<'a, B> {
/// ///
/// SAFETY: The caller is responsible for ensuring that a binding of `size` /// SAFETY: The caller is responsible for ensuring that a binding of `size`
/// bytes starting at `offset` is contained within the buffer. /// bytes starting at `offset` is contained within the buffer.
pub unsafe fn new_unchecked( ///
/// 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
/// is resolved, the argument should just match the type of the member.
/// TODO(<https://github.com/gfx-rs/wgpu/issues/3170>): remove the parameter
pub unsafe fn new_unchecked<S: Into<wgt::BufferSizeOrZero>>(
buffer: &'a B, buffer: &'a B,
offset: wgt::BufferAddress, offset: wgt::BufferAddress,
size: wgt::BufferSize, size: S,
) -> Self { ) -> Self {
Self { Self {
buffer, buffer,
offset, offset,
size, size: size.into(),
} }
} }
} }

View File

@ -4,7 +4,7 @@ use alloc::{
borrow::{Cow, ToOwned as _}, borrow::{Cow, ToOwned as _},
vec::Vec, vec::Vec,
}; };
use core::ops::Range; use core::{num::NonZeroU64, 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,9 +977,11 @@ 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);
self.state // https://github.com/gfx-rs/wgpu/issues/3170
.vertex_buffer_size_map let size =
.insert(buffer_index, binding.size); NonZeroU64::new(binding.size).expect("zero-size vertex buffers are not supported");
self.state.vertex_buffer_size_map.insert(buffer_index, size);
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::{ptr::NonNull, sync::atomic}; use core::{num::NonZeroU64, ptr::NonNull, sync::atomic};
use std::{thread, time}; use std::{thread, time};
use parking_lot::Mutex; use parking_lot::Mutex;
@ -928,9 +928,12 @@ 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
let source_size = NonZeroU64::new(source.size)
.expect("zero-size bindings are not supported");
let binding_size = match ty { let binding_size = match ty {
wgt::BufferBindingType::Storage { .. } => { wgt::BufferBindingType::Storage { .. } => {
Some(source.size) Some(source_size)
} }
_ => None, _ => None,
}; };

View File

@ -1799,10 +1799,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.get()) .range(binding.size)
}, },
)); ));
write.buffer_info(local_buffer_infos) write.buffer_info(local_buffer_infos)

View File

@ -61,6 +61,12 @@ 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.