From b679342d9e62e50eff307d785590ffcbcc39b0d8 Mon Sep 17 00:00:00 2001 From: Connor Fitzgerald Date: Fri, 24 Sep 2021 16:45:02 -0400 Subject: [PATCH] Support descriptor arrays shorter than binding length --- wgpu-core/src/binding_model.rs | 10 +- wgpu-core/src/conv.rs | 9 ++ wgpu-core/src/device/mod.rs | 79 +++++---- wgpu-core/src/instance.rs | 10 +- wgpu-hal/examples/halmark/main.rs | 15 +- wgpu-hal/src/dx12/adapter.rs | 1 + wgpu-hal/src/empty.rs | 6 +- wgpu-hal/src/gles/adapter.rs | 1 + wgpu-hal/src/lib.rs | 16 +- wgpu-hal/src/metal/adapter.rs | 1 + wgpu-hal/src/vulkan/adapter.rs | 255 +++++++++++++++++++++++------- wgpu-hal/src/vulkan/device.rs | 84 +++++++++- wgpu-hal/src/vulkan/mod.rs | 78 +++++++++ wgpu-types/src/lib.rs | 38 +++-- 14 files changed, 482 insertions(+), 121 deletions(-) diff --git a/wgpu-core/src/binding_model.rs b/wgpu-core/src/binding_model.rs index e2c7472c6..b5d318064 100644 --- a/wgpu-core/src/binding_model.rs +++ b/wgpu-core/src/binding_model.rs @@ -65,8 +65,16 @@ pub enum CreateBindGroupError { InvalidTextureView(TextureViewId), #[error("sampler {0:?} is invalid")] InvalidSampler(SamplerId), - #[error("binding count declared with {expected} items, but {actual} items were provided")] + #[error( + "binding count declared with at most {expected} items, but {actual} items were provided" + )] + BindingArrayPartialLengthMismatch { actual: usize, expected: usize }, + #[error( + "binding count declared with exactly {expected} items, but {actual} items were provided" + )] BindingArrayLengthMismatch { actual: usize, expected: usize }, + #[error("array binding provided zero elements")] + BindingArrayZeroLength, #[error("bound buffer range {range:?} does not fit in buffer of size {size}")] BindingRangeTooLarge { buffer: BufferId, diff --git a/wgpu-core/src/conv.rs b/wgpu-core/src/conv.rs index 119c27179..b6632b590 100644 --- a/wgpu-core/src/conv.rs +++ b/wgpu-core/src/conv.rs @@ -152,3 +152,12 @@ pub fn check_texture_dimension_size( Ok(()) } + +pub fn bind_group_layout_flags(features: wgt::Features) -> hal::BindGroupLayoutFlags { + let mut flags = hal::BindGroupLayoutFlags::empty(); + flags.set( + hal::BindGroupLayoutFlags::PARTIALLY_BOUND, + features.contains(wgt::Features::PARTIALLY_BOUND_BINDING_ARRAY), + ); + flags +} diff --git a/wgpu-core/src/device/mod.rs b/wgpu-core/src/device/mod.rs index 3bbeb1b3a..36a59400f 100644 --- a/wgpu-core/src/device/mod.rs +++ b/wgpu-core/src/device/mod.rs @@ -19,7 +19,7 @@ use smallvec::SmallVec; use thiserror::Error; use wgt::{BufferAddress, TextureFormat, TextureViewDimension}; -use std::{borrow::Cow, iter, marker::PhantomData, mem, ops::Range, ptr}; +use std::{borrow::Cow, iter, marker::PhantomData, mem, num::NonZeroU32, ops::Range, ptr}; mod life; pub mod queue; @@ -1186,10 +1186,13 @@ impl Device { })?; } + let bgl_flags = conv::bind_group_layout_flags(self.features); + let mut hal_bindings = entry_map.values().cloned().collect::>(); hal_bindings.sort_by_key(|b| b.binding); let hal_desc = hal::BindGroupLayoutDescriptor { label, + flags: bgl_flags, entries: &hal_bindings, }; let raw = unsafe { @@ -1390,7 +1393,7 @@ impl Device { .entries .get(&binding) .ok_or(Error::MissingBindingDeclaration(binding))?; - let res_index = match entry.resource { + let (res_index, count) = match entry.resource { Br::Buffer(ref bb) => { let bb = Self::create_buffer_binding( bb, @@ -1405,21 +1408,11 @@ impl Device { let res_index = hal_buffers.len(); hal_buffers.push(bb); - res_index + (res_index, 1) } Br::BufferArray(ref bindings_array) => { - if let Some(count) = decl.count { - let count = count.get() as usize; - let num_bindings = bindings_array.len(); - if count != num_bindings { - return Err(Error::BindingArrayLengthMismatch { - actual: num_bindings, - expected: count, - }); - } - } else { - return Err(Error::SingleBindingExpected); - } + let num_bindings = bindings_array.len(); + Self::check_array_binding(self.features, decl.count, num_bindings)?; let res_index = hal_buffers.len(); for bb in bindings_array.iter() { @@ -1435,7 +1428,7 @@ impl Device { )?; hal_buffers.push(bb); } - res_index + (res_index, num_bindings) } Br::Sampler(id) => { match decl.ty { @@ -1467,7 +1460,7 @@ impl Device { let res_index = hal_samplers.len(); hal_samplers.push(&sampler.raw); - res_index + (res_index, 1) } _ => { return Err(Error::WrongBindingType { @@ -1508,21 +1501,11 @@ impl Device { view: &view.raw, usage: internal_use, }); - res_index + (res_index, 1) } Br::TextureViewArray(ref bindings_array) => { - if let Some(count) = decl.count { - let count = count.get() as usize; - let num_bindings = bindings_array.len(); - if count != num_bindings { - return Err(Error::BindingArrayLengthMismatch { - actual: num_bindings, - expected: count, - }); - } - } else { - return Err(Error::SingleBindingExpected); - } + let num_bindings = bindings_array.len(); + Self::check_array_binding(self.features, decl.count, num_bindings)?; let res_index = hal_textures.len(); for &id in bindings_array.iter() { @@ -1554,13 +1537,14 @@ impl Device { }); } - res_index + (res_index, num_bindings) } }; hal_entries.push(hal::BindGroupEntry { binding, resource_index: res_index as u32, + count: count as u32, }); } @@ -1599,6 +1583,39 @@ impl Device { }) } + fn check_array_binding( + features: wgt::Features, + count: Option, + num_bindings: usize, + ) -> Result<(), super::binding_model::CreateBindGroupError> { + use super::binding_model::CreateBindGroupError as Error; + + if let Some(count) = count { + let count = count.get() as usize; + if count < num_bindings { + return Err(Error::BindingArrayPartialLengthMismatch { + actual: num_bindings, + expected: count, + }); + } + if count != num_bindings + && !features.contains(wgt::Features::PARTIALLY_BOUND_BINDING_ARRAY) + { + return Err(Error::BindingArrayLengthMismatch { + actual: num_bindings, + expected: count, + }); + } + if num_bindings == 0 { + return Err(Error::BindingArrayZeroLength); + } + } else { + return Err(Error::SingleBindingExpected); + }; + + Ok(()) + } + fn texture_use_parameters( binding: u32, decl: &wgt::BindGroupLayoutEntry, diff --git a/wgpu-core/src/instance.rs b/wgpu-core/src/instance.rs index 7389a79b8..d73461c6f 100644 --- a/wgpu-core/src/instance.rs +++ b/wgpu-core/src/instance.rs @@ -334,10 +334,12 @@ impl Adapter { desc: &DeviceDescriptor, trace_path: Option<&std::path::Path>, ) -> Result, RequestDeviceError> { - let open = unsafe { self.raw.adapter.open(desc.features) }.map_err(|err| match err { - hal::DeviceError::Lost => RequestDeviceError::DeviceLost, - hal::DeviceError::OutOfMemory => RequestDeviceError::OutOfMemory, - })?; + let open = unsafe { self.raw.adapter.open(desc.features, &desc.limits) }.map_err( + |err| match err { + hal::DeviceError::Lost => RequestDeviceError::DeviceLost, + hal::DeviceError::OutOfMemory => RequestDeviceError::OutOfMemory, + }, + )?; self.create_device_from_hal(self_id, open, desc, trace_path) } diff --git a/wgpu-hal/examples/halmark/main.rs b/wgpu-hal/examples/halmark/main.rs index 4f9dbbdfc..45b9b3ba8 100644 --- a/wgpu-hal/examples/halmark/main.rs +++ b/wgpu-hal/examples/halmark/main.rs @@ -102,8 +102,11 @@ impl Example { ); (exposed.adapter, exposed.capabilities) }; - let hal::OpenDevice { device, mut queue } = - unsafe { adapter.open(wgt::Features::empty()).unwrap() }; + let hal::OpenDevice { device, mut queue } = unsafe { + adapter + .open(wgt::Features::empty(), &wgt::Limits::default()) + .unwrap() + }; let window_size: (u32, u32) = window.inner_size().into(); let surface_config = hal::SurfaceConfiguration { @@ -149,6 +152,7 @@ impl Example { let global_bgl_desc = hal::BindGroupLayoutDescriptor { label: None, + flags: hal::BindGroupLayoutFlags::empty(), entries: &[ wgt::BindGroupLayoutEntry { binding: 0, @@ -186,6 +190,8 @@ impl Example { unsafe { device.create_bind_group_layout(&global_bgl_desc).unwrap() }; let local_bgl_desc = hal::BindGroupLayoutDescriptor { + label: None, + flags: hal::BindGroupLayoutFlags::empty(), entries: &[wgt::BindGroupLayoutEntry { binding: 0, visibility: wgt::ShaderStages::VERTEX, @@ -196,7 +202,6 @@ impl Example { }, count: None, }], - label: None, }; let local_group_layout = unsafe { device.create_bind_group_layout(&local_bgl_desc).unwrap() }; @@ -420,14 +425,17 @@ impl Example { hal::BindGroupEntry { binding: 0, resource_index: 0, + count: 1, }, hal::BindGroupEntry { binding: 1, resource_index: 0, + count: 1, }, hal::BindGroupEntry { binding: 2, resource_index: 0, + count: 1, }, ], }; @@ -449,6 +457,7 @@ impl Example { entries: &[hal::BindGroupEntry { binding: 0, resource_index: 0, + count: 1, }], }; unsafe { device.create_bind_group(&local_group_desc).unwrap() } diff --git a/wgpu-hal/src/dx12/adapter.rs b/wgpu-hal/src/dx12/adapter.rs index ee33a1b1f..efed2da65 100644 --- a/wgpu-hal/src/dx12/adapter.rs +++ b/wgpu-hal/src/dx12/adapter.rs @@ -262,6 +262,7 @@ impl crate::Adapter for super::Adapter { unsafe fn open( &self, features: wgt::Features, + _limits: &wgt::Limits, ) -> Result, crate::DeviceError> { let queue = self .device diff --git a/wgpu-hal/src/empty.rs b/wgpu-hal/src/empty.rs index ad16205aa..2fb446912 100644 --- a/wgpu-hal/src/empty.rs +++ b/wgpu-hal/src/empty.rs @@ -74,7 +74,11 @@ impl crate::Surface for Context { } impl crate::Adapter for Context { - unsafe fn open(&self, features: wgt::Features) -> DeviceResult> { + unsafe fn open( + &self, + features: wgt::Features, + _limits: &wgt::Limits, + ) -> DeviceResult> { Err(crate::DeviceError::Lost) } unsafe fn texture_format_capabilities( diff --git a/wgpu-hal/src/gles/adapter.rs b/wgpu-hal/src/gles/adapter.rs index 6271c2625..373866623 100644 --- a/wgpu-hal/src/gles/adapter.rs +++ b/wgpu-hal/src/gles/adapter.rs @@ -419,6 +419,7 @@ impl crate::Adapter for super::Adapter { unsafe fn open( &self, features: wgt::Features, + _limits: &wgt::Limits, ) -> Result, crate::DeviceError> { let gl = &self.shared.context.lock(); gl.pixel_store_i32(glow::UNPACK_ALIGNMENT, 1); diff --git a/wgpu-hal/src/lib.rs b/wgpu-hal/src/lib.rs index c38f76077..d776b85d6 100644 --- a/wgpu-hal/src/lib.rs +++ b/wgpu-hal/src/lib.rs @@ -188,7 +188,11 @@ pub trait Surface: Send + Sync { } pub trait Adapter: Send + Sync { - unsafe fn open(&self, features: wgt::Features) -> Result, DeviceError>; + unsafe fn open( + &self, + features: wgt::Features, + limits: &wgt::Limits, + ) -> Result, DeviceError>; /// Return the set of supported capabilities for a texture format. unsafe fn texture_format_capabilities( @@ -524,6 +528,14 @@ bitflags!( } ); +bitflags!( + /// Pipeline layout creation flags. + pub struct BindGroupLayoutFlags: u32 { + /// Allows for bind group binding arrays to be shorter than the array in the BGL. + const PARTIALLY_BOUND = 1 << 0; + } +); + bitflags!( /// Texture format capability flags. pub struct TextureFormatCapabilities: u32 { @@ -798,6 +810,7 @@ pub struct SamplerDescriptor<'a> { #[derive(Clone, Debug)] pub struct BindGroupLayoutDescriptor<'a> { pub label: Label<'a>, + pub flags: BindGroupLayoutFlags, pub entries: &'a [wgt::BindGroupLayoutEntry], } @@ -847,6 +860,7 @@ impl Clone for TextureBinding<'_, A> { pub struct BindGroupEntry { pub binding: u32, pub resource_index: u32, + pub count: u32, } /// BindGroup descriptor. diff --git a/wgpu-hal/src/metal/adapter.rs b/wgpu-hal/src/metal/adapter.rs index dbe0abf9d..dbf4f1a13 100644 --- a/wgpu-hal/src/metal/adapter.rs +++ b/wgpu-hal/src/metal/adapter.rs @@ -19,6 +19,7 @@ impl crate::Adapter for super::Adapter { unsafe fn open( &self, features: wgt::Features, + _limits: &wgt::Limits, ) -> Result, crate::DeviceError> { let queue = self .shared diff --git a/wgpu-hal/src/vulkan/adapter.rs b/wgpu-hal/src/vulkan/adapter.rs index 89024b446..7cfe65b8a 100644 --- a/wgpu-hal/src/vulkan/adapter.rs +++ b/wgpu-hal/src/vulkan/adapter.rs @@ -7,15 +7,17 @@ use std::{ffi::CStr, mem, ptr, sync::Arc}; //TODO: const fn? fn indexing_features() -> wgt::Features { - wgt::Features::BUFFER_BINDING_ARRAY | wgt::Features::TEXTURE_BINDING_ARRAY + wgt::Features::SAMPLED_TEXTURE_AND_STORAGE_BUFFER_ARRAY_NON_UNIFORM_INDEXING + | wgt::Features::UNIFORM_BUFFER_AND_STORAGE_TEXTURE_ARRAY_NON_UNIFORM_INDEXING + | wgt::Features::UNSIZED_BINDING_ARRAY } /// Aggregate of the `vk::PhysicalDevice*Features` structs used by `gfx`. #[derive(Debug, Default)] pub struct PhysicalDeviceFeatures { core: vk::PhysicalDeviceFeatures, - vulkan_1_2: Option, - descriptor_indexing: Option, + pub(super) vulkan_1_2: Option, + pub(super) descriptor_indexing: Option, imageless_framebuffer: Option, timeline_semaphore: Option, image_robustness: Option, @@ -54,7 +56,29 @@ impl PhysicalDeviceFeatures { requested_features: wgt::Features, downlevel_flags: wgt::DownlevelFlags, private_caps: &super::PrivateCapabilities, + uab_types: super::UpdateAfterBindTypes, ) -> Self { + let needs_sampled_image_non_uniform = requested_features.contains( + wgt::Features::TEXTURE_BINDING_ARRAY + | wgt::Features::SAMPLED_TEXTURE_AND_STORAGE_BUFFER_ARRAY_NON_UNIFORM_INDEXING, + ); + let needs_storage_buffer_non_uniform = requested_features.contains( + wgt::Features::BUFFER_BINDING_ARRAY + | wgt::Features::STORAGE_RESOURCE_BINDING_ARRAY + | wgt::Features::SAMPLED_TEXTURE_AND_STORAGE_BUFFER_ARRAY_NON_UNIFORM_INDEXING, + ); + let needs_uniform_buffer_non_uniform = requested_features.contains( + wgt::Features::TEXTURE_BINDING_ARRAY + | wgt::Features::UNIFORM_BUFFER_AND_STORAGE_TEXTURE_ARRAY_NON_UNIFORM_INDEXING, + ); + let needs_storage_image_non_uniform = requested_features.contains( + wgt::Features::TEXTURE_BINDING_ARRAY + | wgt::Features::STORAGE_RESOURCE_BINDING_ARRAY + | wgt::Features::UNIFORM_BUFFER_AND_STORAGE_TEXTURE_ARRAY_NON_UNIFORM_INDEXING, + ); + let needs_partially_bound = + requested_features.intersects(wgt::Features::PARTIALLY_BOUND_BINDING_ARRAY); + Self { // vk::PhysicalDeviceFeatures is a struct composed of Bool32's while // Features is a bitfield so we need to map everything manually @@ -132,32 +156,30 @@ impl PhysicalDeviceFeatures { ) .descriptor_indexing(requested_features.intersects(indexing_features())) .shader_sampled_image_array_non_uniform_indexing( - requested_features.contains( - wgt::Features::TEXTURE_BINDING_ARRAY - | wgt::Features::SAMPLED_TEXTURE_AND_STORAGE_BUFFER_ARRAY_NON_UNIFORM_INDEXING, - ), + needs_sampled_image_non_uniform, ) .shader_storage_image_array_non_uniform_indexing( - requested_features.contains( - wgt::Features::TEXTURE_BINDING_ARRAY - | wgt::Features::STORAGE_RESOURCE_BINDING_ARRAY - | wgt::Features::UNIFORM_BUFFER_AND_STORAGE_TEXTURE_ARRAY_NON_UNIFORM_INDEXING, - ), + needs_storage_image_non_uniform, ) - //.shader_storage_buffer_array_non_uniform_indexing( .shader_uniform_buffer_array_non_uniform_indexing( - requested_features.contains( - wgt::Features::BUFFER_BINDING_ARRAY - | wgt::Features::UNIFORM_BUFFER_AND_STORAGE_TEXTURE_ARRAY_NON_UNIFORM_INDEXING, - ), + needs_uniform_buffer_non_uniform, ) .shader_storage_buffer_array_non_uniform_indexing( - requested_features.contains( - wgt::Features::BUFFER_BINDING_ARRAY - | wgt::Features::STORAGE_RESOURCE_BINDING_ARRAY - | wgt::Features::SAMPLED_TEXTURE_AND_STORAGE_BUFFER_ARRAY_NON_UNIFORM_INDEXING, - ), + needs_storage_buffer_non_uniform, ) + .descriptor_binding_sampled_image_update_after_bind( + uab_types.contains(super::UpdateAfterBindTypes::SAMPLED_TEXTURE), + ) + .descriptor_binding_storage_image_update_after_bind( + uab_types.contains(super::UpdateAfterBindTypes::STORAGE_TEXTURE), + ) + .descriptor_binding_uniform_buffer_update_after_bind( + uab_types.contains(super::UpdateAfterBindTypes::UNIFORM_BUFFER), + ) + .descriptor_binding_storage_buffer_update_after_bind( + uab_types.contains(super::UpdateAfterBindTypes::STORAGE_BUFFER), + ) + .descriptor_binding_partially_bound(needs_partially_bound) .runtime_descriptor_array( requested_features.contains(wgt::Features::UNSIZED_BINDING_ARRAY), ) @@ -175,32 +197,30 @@ impl PhysicalDeviceFeatures { Some( vk::PhysicalDeviceDescriptorIndexingFeaturesEXT::builder() .shader_sampled_image_array_non_uniform_indexing( - requested_features.contains( - wgt::Features::TEXTURE_BINDING_ARRAY - | wgt::Features::SAMPLED_TEXTURE_AND_STORAGE_BUFFER_ARRAY_NON_UNIFORM_INDEXING, - ), + needs_sampled_image_non_uniform, ) .shader_storage_image_array_non_uniform_indexing( - requested_features.contains( - wgt::Features::TEXTURE_BINDING_ARRAY - | wgt::Features::STORAGE_RESOURCE_BINDING_ARRAY - | wgt::Features::UNIFORM_BUFFER_AND_STORAGE_TEXTURE_ARRAY_NON_UNIFORM_INDEXING, - ), + needs_storage_image_non_uniform, ) - //.shader_storage_buffer_array_non_uniform_indexing( .shader_uniform_buffer_array_non_uniform_indexing( - requested_features.contains( - wgt::Features::BUFFER_BINDING_ARRAY - | wgt::Features::UNIFORM_BUFFER_AND_STORAGE_TEXTURE_ARRAY_NON_UNIFORM_INDEXING, - ), + needs_uniform_buffer_non_uniform, ) .shader_storage_buffer_array_non_uniform_indexing( - requested_features.contains( - wgt::Features::BUFFER_BINDING_ARRAY - | wgt::Features::STORAGE_RESOURCE_BINDING_ARRAY - | wgt::Features::SAMPLED_TEXTURE_AND_STORAGE_BUFFER_ARRAY_NON_UNIFORM_INDEXING, - ), + needs_storage_buffer_non_uniform, ) + .descriptor_binding_sampled_image_update_after_bind( + uab_types.contains(super::UpdateAfterBindTypes::SAMPLED_TEXTURE), + ) + .descriptor_binding_storage_image_update_after_bind( + uab_types.contains(super::UpdateAfterBindTypes::STORAGE_TEXTURE), + ) + .descriptor_binding_uniform_buffer_update_after_bind( + uab_types.contains(super::UpdateAfterBindTypes::UNIFORM_BUFFER), + ) + .descriptor_binding_storage_buffer_update_after_bind( + uab_types.contains(super::UpdateAfterBindTypes::STORAGE_BUFFER), + ) + .descriptor_binding_partially_bound(needs_partially_bound) .runtime_descriptor_array( requested_features.contains(wgt::Features::UNSIZED_BINDING_ARRAY), ) @@ -380,6 +400,9 @@ impl PhysicalDeviceFeatures { if vulkan_1_2.runtime_descriptor_array != 0 { features |= F::UNSIZED_BINDING_ARRAY; } + if vulkan_1_2.descriptor_binding_partially_bound != 0 { + features |= F::PARTIALLY_BOUND_BINDING_ARRAY; + } //if vulkan_1_2.sampler_mirror_clamp_to_edge != 0 { //if vulkan_1_2.sampler_filter_minmax != 0 { if vulkan_1_2.draw_indirect_count != 0 { @@ -419,6 +442,9 @@ impl PhysicalDeviceFeatures { ) { features.insert(F::UNIFORM_BUFFER_AND_STORAGE_TEXTURE_ARRAY_NON_UNIFORM_INDEXING); } + if descriptor_indexing.descriptor_binding_partially_bound != 0 { + features |= F::PARTIALLY_BOUND_BINDING_ARRAY; + } if descriptor_indexing.runtime_descriptor_array != 0 { features |= F::UNSIZED_BINDING_ARRAY; } @@ -438,11 +464,18 @@ impl PhysicalDeviceFeatures { } /// Information gathered about a physical device capabilities. +#[derive(Default)] pub struct PhysicalDeviceCapabilities { supported_extensions: Vec, properties: vk::PhysicalDeviceProperties, + vulkan_1_2: Option, + descriptor_indexing: Option, } +// This is safe because the structs have `p_next: *mut c_void`, which we null out/never read. +unsafe impl Send for PhysicalDeviceCapabilities {} +unsafe impl Sync for PhysicalDeviceCapabilities {} + impl PhysicalDeviceCapabilities { fn supports_extension(&self, extension: &CStr) -> bool { self.supported_extensions @@ -498,8 +531,63 @@ impl PhysicalDeviceCapabilities { extensions } - fn to_wgpu_limits(&self) -> wgt::Limits { + fn to_wgpu_limits(&self, features: &PhysicalDeviceFeatures) -> wgt::Limits { let limits = &self.properties.limits; + + let uab_types = super::UpdateAfterBindTypes::from_features(features); + + let max_sampled_textures = + if uab_types.contains(super::UpdateAfterBindTypes::SAMPLED_TEXTURE) { + if let Some(di) = self.descriptor_indexing { + di.max_per_stage_descriptor_update_after_bind_sampled_images + } else if let Some(vk_1_2) = self.vulkan_1_2 { + vk_1_2.max_per_stage_descriptor_update_after_bind_sampled_images + } else { + limits.max_per_stage_descriptor_sampled_images + } + } else { + limits.max_per_stage_descriptor_sampled_images + }; + + let max_storage_textures = + if uab_types.contains(super::UpdateAfterBindTypes::STORAGE_TEXTURE) { + if let Some(di) = self.descriptor_indexing { + di.max_per_stage_descriptor_update_after_bind_storage_images + } else if let Some(vk_1_2) = self.vulkan_1_2 { + vk_1_2.max_per_stage_descriptor_update_after_bind_storage_images + } else { + limits.max_per_stage_descriptor_storage_images + } + } else { + limits.max_per_stage_descriptor_storage_images + }; + + let max_uniform_buffers = if uab_types.contains(super::UpdateAfterBindTypes::UNIFORM_BUFFER) + { + if let Some(di) = self.descriptor_indexing { + di.max_per_stage_descriptor_update_after_bind_uniform_buffers + } else if let Some(vk_1_2) = self.vulkan_1_2 { + vk_1_2.max_per_stage_descriptor_update_after_bind_uniform_buffers + } else { + limits.max_per_stage_descriptor_uniform_buffers + } + } else { + limits.max_per_stage_descriptor_uniform_buffers + }; + + let max_storage_buffers = if uab_types.contains(super::UpdateAfterBindTypes::STORAGE_BUFFER) + { + if let Some(di) = self.descriptor_indexing { + di.max_per_stage_descriptor_update_after_bind_storage_buffers + } else if let Some(vk_1_2) = self.vulkan_1_2 { + vk_1_2.max_per_stage_descriptor_update_after_bind_storage_buffers + } else { + limits.max_per_stage_descriptor_storage_buffers + } + } else { + limits.max_per_stage_descriptor_storage_buffers + }; + wgt::Limits { max_texture_dimension_1d: limits.max_image_dimension1_d, max_texture_dimension_2d: limits.max_image_dimension2_d, @@ -512,11 +600,11 @@ impl PhysicalDeviceCapabilities { .max_descriptor_set_uniform_buffers_dynamic, max_dynamic_storage_buffers_per_pipeline_layout: limits .max_descriptor_set_storage_buffers_dynamic, - max_sampled_textures_per_shader_stage: limits.max_per_stage_descriptor_sampled_images, + max_sampled_textures_per_shader_stage: max_sampled_textures, max_samplers_per_shader_stage: limits.max_per_stage_descriptor_samplers, - max_storage_buffers_per_shader_stage: limits.max_per_stage_descriptor_storage_buffers, - max_storage_textures_per_shader_stage: limits.max_per_stage_descriptor_storage_images, - max_uniform_buffers_per_shader_stage: limits.max_per_stage_descriptor_uniform_buffers, + max_storage_buffers_per_shader_stage: max_storage_buffers, + max_storage_textures_per_shader_stage: max_storage_textures, + max_uniform_buffers_per_shader_stage: max_uniform_buffers, max_uniform_buffer_binding_size: limits.max_uniform_buffer_range, max_storage_buffer_binding_size: limits.max_storage_buffer_range, max_vertex_buffers: limits @@ -547,11 +635,46 @@ impl super::InstanceShared { &self, phd: vk::PhysicalDevice, ) -> (PhysicalDeviceCapabilities, PhysicalDeviceFeatures) { - let capabilities = unsafe { - PhysicalDeviceCapabilities { - supported_extensions: self.raw.enumerate_device_extension_properties(phd).unwrap(), - properties: self.raw.get_physical_device_properties(phd), - } + let capabilities = { + let mut capabilities = PhysicalDeviceCapabilities::default(); + capabilities.supported_extensions = + unsafe { self.raw.enumerate_device_extension_properties(phd).unwrap() }; + capabilities.properties = if let Some(ref get_device_properties) = + self.get_physical_device_properties + { + let core = vk::PhysicalDeviceProperties::builder().build(); + let mut properites2 = vk::PhysicalDeviceProperties2::builder() + .properties(core) + .build(); + + if capabilities.properties.api_version >= vk::API_VERSION_1_2 { + capabilities.vulkan_1_2 = + Some(vk::PhysicalDeviceVulkan12Properties::builder().build()); + + let mut_ref = capabilities.vulkan_1_2.as_mut().unwrap(); + mut_ref.p_next = + mem::replace(&mut properites2.p_next, mut_ref as *mut _ as *mut _); + } + + if capabilities.supports_extension(vk::ExtDescriptorIndexingFn::name()) { + capabilities.descriptor_indexing = + Some(vk::PhysicalDeviceDescriptorIndexingPropertiesEXT::builder().build()); + + let mut_ref = capabilities.descriptor_indexing.as_mut().unwrap(); + mut_ref.p_next = + mem::replace(&mut properites2.p_next, mut_ref as *mut _ as *mut _); + } + + unsafe { + get_device_properties + .get_physical_device_properties2_khr(phd, &mut properites2); + } + properites2.properties + } else { + unsafe { self.raw.get_physical_device_properties(phd) } + }; + + capabilities }; let mut features = PhysicalDeviceFeatures::default(); @@ -645,6 +768,8 @@ impl super::Instance { &self, phd: vk::PhysicalDevice, ) -> Option> { + use crate::auxil::db; + let (phd_capabilities, phd_features) = self.shared.inspect(phd); let info = wgt::AdapterInfo { @@ -670,7 +795,6 @@ impl super::Instance { let (available_features, downlevel_flags) = phd_features.to_wgpu(&phd_capabilities); let mut workarounds = super::Workarounds::empty(); { - use crate::auxil::db; // see https://github.com/gfx-rs/gfx/issues/1930 let _is_windows_intel_dual_src_bug = cfg!(windows) && phd_capabilities.properties.vendor_id == db::intel::VENDOR @@ -752,7 +876,7 @@ impl super::Instance { }; let capabilities = crate::Capabilities { - limits: phd_capabilities.to_wgpu_limits(), + limits: phd_capabilities.to_wgpu_limits(&phd_features), alignments: phd_capabilities.to_hal_alignments(), downlevel: wgt::DownlevelCapabilities { flags: downlevel_flags, @@ -809,6 +933,7 @@ impl super::Adapter { &self, enabled_extensions: &[&'static CStr], features: wgt::Features, + uab_types: super::UpdateAfterBindTypes, ) -> PhysicalDeviceFeatures { PhysicalDeviceFeatures::from_extensions_and_requested_features( self.phd_capabilities.properties.api_version, @@ -816,6 +941,7 @@ impl super::Adapter { features, self.downlevel_flags, &self.private_caps, + uab_types, ) } @@ -824,11 +950,13 @@ impl super::Adapter { /// - `raw_device` must be created from this adapter. /// - `raw_device` must be created using `family_index`, `enabled_extensions` and `physical_device_features()` /// - `enabled_extensions` must be a superset of `required_device_extensions()`. + #[allow(clippy::too_many_arguments)] pub unsafe fn device_from_raw( &self, raw_device: ash::Device, handle_is_owned: bool, enabled_extensions: &[&'static CStr], + uab_types: super::UpdateAfterBindTypes, family_index: u32, queue_index: u32, ) -> Result, crate::DeviceError> { @@ -934,10 +1062,11 @@ impl super::Adapter { timeline_semaphore: timeline_semaphore_fn, }, vendor_id: self.phd_capabilities.properties.vendor_id, + timestamp_period: self.phd_capabilities.properties.limits.timestamp_period, + uab_types, downlevel_flags: self.downlevel_flags, private_caps: self.private_caps.clone(), workarounds: self.workarounds, - timestamp_period: self.phd_capabilities.properties.limits.timestamp_period, render_passes: Mutex::new(Default::default()), framebuffers: Mutex::new(Default::default()), }); @@ -979,7 +1108,15 @@ impl super::Adapter { }; gpu_alloc::GpuAllocator::new(config, properties) }; - let desc_allocator = gpu_descriptor::DescriptorAllocator::new(0); + let desc_allocator = gpu_descriptor::DescriptorAllocator::new( + if let Some(vk_12) = self.phd_capabilities.vulkan_1_2 { + vk_12.max_update_after_bind_descriptors_in_all_pools + } else if let Some(di) = self.phd_capabilities.descriptor_indexing { + di.max_update_after_bind_descriptors_in_all_pools + } else { + 0 + }, + ); let device = super::Device { shared, @@ -999,9 +1136,14 @@ impl crate::Adapter for super::Adapter { unsafe fn open( &self, features: wgt::Features, + limits: &wgt::Limits, ) -> Result, crate::DeviceError> { + let phd_limits = &self.phd_capabilities.properties.limits; + let uab_types = super::UpdateAfterBindTypes::from_limits(limits, phd_limits); + let enabled_extensions = self.required_device_extensions(features); - let mut enabled_phd_features = self.physical_device_features(&enabled_extensions, features); + let mut enabled_phd_features = + self.physical_device_features(&enabled_extensions, features, uab_types); let family_index = 0; //TODO let family_info = vk::DeviceQueueCreateInfo::builder() @@ -1030,6 +1172,7 @@ impl crate::Adapter for super::Adapter { raw_device, true, &enabled_extensions, + uab_types, family_info.queue_family_index, 0, ) diff --git a/wgpu-hal/src/vulkan/device.rs b/wgpu-hal/src/vulkan/device.rs index 8c4edad51..49db16b4b 100644 --- a/wgpu-hal/src/vulkan/device.rs +++ b/wgpu-hal/src/vulkan/device.rs @@ -377,7 +377,12 @@ impl }) .collect::>(); - let mut vk_flags = vk::DescriptorPoolCreateFlags::empty(); + let mut vk_flags = + if flags.contains(gpu_descriptor::DescriptorPoolCreateFlags::UPDATE_AFTER_BIND) { + vk::DescriptorPoolCreateFlags::UPDATE_AFTER_BIND + } else { + vk::DescriptorPoolCreateFlags::empty() + }; if flags.contains(gpu_descriptor::DescriptorPoolCreateFlags::FREE_DESCRIPTOR_SET) { vk_flags |= vk::DescriptorPoolCreateFlags::FREE_DESCRIPTOR_SET; } @@ -1029,9 +1034,69 @@ impl crate::Device for super::Device { }) .collect::>(); - let vk_info = vk::DescriptorSetLayoutCreateInfo::builder() - .flags(vk::DescriptorSetLayoutCreateFlags::empty()) - .bindings(&vk_bindings); + let vk_info = vk::DescriptorSetLayoutCreateInfo::builder().bindings(&vk_bindings); + + let mut binding_flag_info; + let binding_flag_vec; + let mut requires_update_after_bind = false; + + let partially_bound = desc + .flags + .contains(crate::BindGroupLayoutFlags::PARTIALLY_BOUND); + + let vk_info = if !self.shared.uab_types.is_empty() || partially_bound { + binding_flag_vec = desc + .entries + .iter() + .map(|entry| { + let mut flags = vk::DescriptorBindingFlags::empty(); + + if partially_bound && entry.count.is_some() { + flags |= vk::DescriptorBindingFlags::PARTIALLY_BOUND; + } + + let uab_type = match entry.ty { + wgt::BindingType::Buffer { + ty: wgt::BufferBindingType::Uniform, + .. + } => super::UpdateAfterBindTypes::UNIFORM_BUFFER, + wgt::BindingType::Buffer { + ty: wgt::BufferBindingType::Storage { .. }, + .. + } => super::UpdateAfterBindTypes::STORAGE_BUFFER, + wgt::BindingType::Texture { .. } => { + super::UpdateAfterBindTypes::SAMPLED_TEXTURE + } + wgt::BindingType::StorageTexture { .. } => { + super::UpdateAfterBindTypes::STORAGE_TEXTURE + } + _ => super::UpdateAfterBindTypes::empty(), + }; + + if !uab_type.is_empty() && self.shared.uab_types.contains(uab_type) { + flags |= vk::DescriptorBindingFlags::UPDATE_AFTER_BIND; + requires_update_after_bind = true; + } + + flags + }) + .collect::>(); + + binding_flag_info = vk::DescriptorSetLayoutBindingFlagsCreateInfo::builder() + .binding_flags(&binding_flag_vec); + + vk_info.push_next(&mut binding_flag_info) + } else { + vk_info + }; + + let dsl_create_flags = if requires_update_after_bind { + vk::DescriptorSetLayoutCreateFlags::UPDATE_AFTER_BIND_POOL + } else { + vk::DescriptorSetLayoutCreateFlags::empty() + }; + + let vk_info = vk_info.flags(dsl_create_flags); let raw = self .shared @@ -1047,6 +1112,7 @@ impl crate::Device for super::Device { raw, desc_count, types: types.into_boxed_slice(), + requires_update_after_bind, }) } unsafe fn destroy_bind_group_layout(&self, bg_layout: super::BindGroupLayout) { @@ -1102,7 +1168,11 @@ impl crate::Device for super::Device { let mut vk_sets = self.desc_allocator.lock().allocate( &*self.shared, &desc.layout.raw, - gpu_descriptor::DescriptorSetLayoutCreateFlags::empty(), + if desc.layout.requires_update_after_bind { + gpu_descriptor::DescriptorSetLayoutCreateFlags::UPDATE_AFTER_BIND + } else { + gpu_descriptor::DescriptorSetLayoutCreateFlags::empty() + }, &desc.layout.desc_count, 1, )?; @@ -1139,7 +1209,7 @@ impl crate::Device for super::Device { vk::DescriptorType::SAMPLED_IMAGE | vk::DescriptorType::STORAGE_IMAGE => { let index = image_infos.len(); let start = entry.resource_index; - let end = start + size; + let end = start + entry.count; image_infos.extend(desc.textures[start as usize..end as usize].iter().map( |binding| { let layout = @@ -1158,7 +1228,7 @@ impl crate::Device for super::Device { | vk::DescriptorType::STORAGE_BUFFER_DYNAMIC => { let index = buffer_infos.len(); let start = entry.resource_index; - let end = start + size; + let end = start + entry.count; buffer_infos.extend(desc.buffers[start as usize..end as usize].iter().map( |binding| { vk::DescriptorBufferInfo::builder() diff --git a/wgpu-hal/src/vulkan/mod.rs b/wgpu-hal/src/vulkan/mod.rs index be9d120e0..74514ecf4 100644 --- a/wgpu-hal/src/vulkan/mod.rs +++ b/wgpu-hal/src/vulkan/mod.rs @@ -227,6 +227,82 @@ struct FramebufferKey { sample_count: u32, } +bitflags::bitflags! { + pub struct UpdateAfterBindTypes: u8 { + const UNIFORM_BUFFER = 0x1; + const STORAGE_BUFFER = 0x2; + const SAMPLED_TEXTURE = 0x4; + const STORAGE_TEXTURE = 0x8; + } +} + +impl UpdateAfterBindTypes { + fn from_limits(limits: &wgt::Limits, phd_limits: &vk::PhysicalDeviceLimits) -> Self { + let mut uab_types = UpdateAfterBindTypes::empty(); + uab_types.set( + UpdateAfterBindTypes::UNIFORM_BUFFER, + limits.max_uniform_buffers_per_shader_stage + > phd_limits.max_per_stage_descriptor_uniform_buffers, + ); + uab_types.set( + UpdateAfterBindTypes::STORAGE_BUFFER, + limits.max_storage_buffers_per_shader_stage + > phd_limits.max_per_stage_descriptor_storage_buffers, + ); + uab_types.set( + UpdateAfterBindTypes::SAMPLED_TEXTURE, + limits.max_sampled_textures_per_shader_stage + > phd_limits.max_per_stage_descriptor_sampled_images, + ); + uab_types.set( + UpdateAfterBindTypes::STORAGE_TEXTURE, + limits.max_storage_textures_per_shader_stage + > phd_limits.max_per_stage_descriptor_storage_images, + ); + uab_types + } + + fn from_features(features: &adapter::PhysicalDeviceFeatures) -> Self { + let mut uab_types = UpdateAfterBindTypes::empty(); + if let Some(vk_12) = features.vulkan_1_2 { + uab_types.set( + UpdateAfterBindTypes::UNIFORM_BUFFER, + vk_12.descriptor_binding_uniform_buffer_update_after_bind != 0, + ); + uab_types.set( + UpdateAfterBindTypes::STORAGE_BUFFER, + vk_12.descriptor_binding_storage_buffer_update_after_bind != 0, + ); + uab_types.set( + UpdateAfterBindTypes::SAMPLED_TEXTURE, + vk_12.descriptor_binding_sampled_image_update_after_bind != 0, + ); + uab_types.set( + UpdateAfterBindTypes::STORAGE_TEXTURE, + vk_12.descriptor_binding_storage_image_update_after_bind != 0, + ); + } else if let Some(di) = features.descriptor_indexing { + uab_types.set( + UpdateAfterBindTypes::UNIFORM_BUFFER, + di.descriptor_binding_uniform_buffer_update_after_bind != 0, + ); + uab_types.set( + UpdateAfterBindTypes::STORAGE_BUFFER, + di.descriptor_binding_storage_buffer_update_after_bind != 0, + ); + uab_types.set( + UpdateAfterBindTypes::SAMPLED_TEXTURE, + di.descriptor_binding_sampled_image_update_after_bind != 0, + ); + uab_types.set( + UpdateAfterBindTypes::STORAGE_TEXTURE, + di.descriptor_binding_storage_image_update_after_bind != 0, + ); + } + uab_types + } +} + struct DeviceShared { raw: ash::Device, handle_is_owned: bool, @@ -234,6 +310,7 @@ struct DeviceShared { extension_fns: DeviceExtensionFunctions, vendor_id: u32, timestamp_period: f32, + uab_types: UpdateAfterBindTypes, downlevel_flags: wgt::DownlevelFlags, private_caps: PrivateCapabilities, workarounds: Workarounds, @@ -314,6 +391,7 @@ pub struct BindGroupLayout { raw: vk::DescriptorSetLayout, desc_count: gpu_descriptor::DescriptorTotalCount, types: Box<[(vk::DescriptorType, u32)]>, + requires_update_after_bind: bool, } #[derive(Debug)] diff --git a/wgpu-types/src/lib.rs b/wgpu-types/src/lib.rs index 729664d9c..e6ab7b572 100644 --- a/wgpu-types/src/lib.rs +++ b/wgpu-types/src/lib.rs @@ -330,6 +330,10 @@ bitflags::bitflags! { /// /// This is a native only feature. const UNIFORM_BUFFER_AND_STORAGE_TEXTURE_ARRAY_NON_UNIFORM_INDEXING = 1 << 21; + /// Allows the user to create bind groups continaing arrays with less bindings than the BindGroupLayout. + /// + /// This is a native only feature. + const PARTIALLY_BOUND_BINDING_ARRAY = 1 << 22; /// Allows the user to create unsized uniform arrays of bindings: /// /// eg. `uniform texture2D textures[]`. @@ -339,7 +343,7 @@ bitflags::bitflags! { /// - Vulkan 1.2+ (or VK_EXT_descriptor_indexing)'s runtimeDescriptorArray feature /// /// This is a native only feature. - const UNSIZED_BINDING_ARRAY = 1 << 22; + const UNSIZED_BINDING_ARRAY = 1 << 23; /// Allows the user to call [`RenderPass::multi_draw_indirect`] and [`RenderPass::multi_draw_indexed_indirect`]. /// /// Allows multiple indirect calls to be dispatched from a single buffer. @@ -349,7 +353,7 @@ bitflags::bitflags! { /// - Vulkan /// /// This is a native only feature. - const MULTI_DRAW_INDIRECT = 1 << 23; + const MULTI_DRAW_INDIRECT = 1 << 24; /// Allows the user to call [`RenderPass::multi_draw_indirect_count`] and [`RenderPass::multi_draw_indexed_indirect_count`]. /// /// This allows the use of a buffer containing the actual number of draw calls. @@ -359,7 +363,7 @@ bitflags::bitflags! { /// - Vulkan 1.2+ (or VK_KHR_draw_indirect_count) /// /// This is a native only feature. - const MULTI_DRAW_INDIRECT_COUNT = 1 << 24; + const MULTI_DRAW_INDIRECT_COUNT = 1 << 25; /// Allows the use of push constants: small, fast bits of memory that can be updated /// inside a [`RenderPass`]. /// @@ -376,7 +380,7 @@ bitflags::bitflags! { /// - OpenGL (emulated with uniforms) /// /// This is a native only feature. - const PUSH_CONSTANTS = 1 << 25; + const PUSH_CONSTANTS = 1 << 26; /// Allows the use of [`AddressMode::ClampToBorder`]. /// /// Supported platforms: @@ -387,7 +391,7 @@ bitflags::bitflags! { /// - OpenGL /// /// This is a web and native feature. - const ADDRESS_MODE_CLAMP_TO_BORDER = 1 << 26; + const ADDRESS_MODE_CLAMP_TO_BORDER = 1 << 27; /// Allows the user to set [`PolygonMode::Line`] in [`PrimitiveState::polygon_mode`] /// /// This allows drawing polygons/triangles as lines (wireframe) instead of filled @@ -398,7 +402,7 @@ bitflags::bitflags! { /// - Metal /// /// This is a native only feature. - const POLYGON_MODE_LINE= 1 << 27; + const POLYGON_MODE_LINE= 1 << 28; /// Allows the user to set [`PolygonMode::Point`] in [`PrimitiveState::polygon_mode`] /// /// This allows only drawing the vertices of polygons/triangles instead of filled @@ -408,7 +412,7 @@ bitflags::bitflags! { /// - Vulkan /// /// This is a native only feature. - const POLYGON_MODE_POINT = 1 << 28; + const POLYGON_MODE_POINT = 1 << 29; /// Enables ETC family of compressed textures. All ETC textures use 4x4 pixel blocks. /// ETC2 RGB and RGBA1 are 8 bytes per block. RTC2 RGBA8 and EAC are 16 bytes per block. /// @@ -423,7 +427,7 @@ bitflags::bitflags! { /// - Mobile (some) /// /// This is a native-only feature. - const TEXTURE_COMPRESSION_ETC2 = 1 << 29; + const TEXTURE_COMPRESSION_ETC2 = 1 << 30; /// Enables ASTC family of compressed textures. ASTC textures use pixel blocks varying from 4x4 to 12x12. /// Blocks are always 16 bytes. /// @@ -438,7 +442,7 @@ bitflags::bitflags! { /// - Mobile (some) /// /// This is a native-only feature. - const TEXTURE_COMPRESSION_ASTC_LDR = 1 << 30; + const TEXTURE_COMPRESSION_ASTC_LDR = 1 << 31; /// Enables device specific texture format features. /// /// See `TextureFormatFeatures` for a listing of the features in question. @@ -450,7 +454,7 @@ bitflags::bitflags! { /// This extension does not enable additional formats. /// /// This is a native-only feature. - const TEXTURE_ADAPTER_SPECIFIC_FORMAT_FEATURES = 1 << 31; + const TEXTURE_ADAPTER_SPECIFIC_FORMAT_FEATURES = 1 << 32; /// Enables 64-bit floating point types in SPIR-V shaders. /// /// Note: even when supported by GPU hardware, 64-bit floating point operations are @@ -460,7 +464,7 @@ bitflags::bitflags! { /// - Vulkan /// /// This is a native-only feature. - const SHADER_FLOAT64 = 1 << 32; + const SHADER_FLOAT64 = 1 << 33; /// Enables using 64-bit types for vertex attributes. /// /// Requires SHADER_FLOAT64. @@ -468,7 +472,7 @@ bitflags::bitflags! { /// Supported Platforms: N/A /// /// This is a native-only feature. - const VERTEX_ATTRIBUTE_64BIT = 1 << 33; + const VERTEX_ATTRIBUTE_64BIT = 1 << 34; /// Allows the user to set a overestimation-conservative-rasterization in [`PrimitiveState::conservative`] /// /// Processing of degenerate triangles/lines is hardware specific. @@ -478,7 +482,7 @@ bitflags::bitflags! { /// - Vulkan /// /// This is a native only feature. - const CONSERVATIVE_RASTERIZATION = 1 << 34; + const CONSERVATIVE_RASTERIZATION = 1 << 35; /// Enables bindings of writable storage buffers and textures visible to vertex shaders. /// /// Note: some (tiled-based) platforms do not support vertex shaders with any side-effects. @@ -487,14 +491,14 @@ bitflags::bitflags! { /// - All /// /// This is a native-only feature. - const VERTEX_WRITABLE_STORAGE = 1 << 35; + const VERTEX_WRITABLE_STORAGE = 1 << 36; /// Enables clear to zero for buffers & textures. /// /// Supported platforms: /// - All /// /// This is a native only feature. - const CLEAR_COMMANDS = 1 << 36; + const CLEAR_COMMANDS = 1 << 37; /// Enables creating shader modules from SPIR-V binary data (unsafe). /// /// SPIR-V data is not parsed or interpreted in any way; you can use @@ -506,7 +510,7 @@ bitflags::bitflags! { /// Vulkan implementation. /// /// This is a native only feature. - const SPIRV_SHADER_PASSTHROUGH = 1 << 37; + const SPIRV_SHADER_PASSTHROUGH = 1 << 38; /// Enables `builtin(primitive_index)` in fragment shaders. /// /// Note: enables geometry processing for pipelines using the builtin. @@ -517,7 +521,7 @@ bitflags::bitflags! { /// - Vulkan /// /// This is a native only feature. - const SHADER_PRIMITIVE_INDEX = 1 << 38; + const SHADER_PRIMITIVE_INDEX = 1 << 39; } }