Skip to content

Commit 27f32dc

Browse files
committed
[vulkan, naga spv-out] Remap resource bindings
In order to support external textures, we must be able to map a single external texture resource binding to multiple Vulkan descriptors. This means we must be able to override the `Binding` and `DescriptorSet` values for global variables when generating SPIR-V, rather than simply passing through the group and binding values from Naga IR. This patch extends the existing SPIR-V Naga backend's `BindingMap` to contain a descriptor set and binding value in addition to the existing array size. When creating BindGroupLayouts/BindGroups we use a sequentially incrementing value for each entry's binding value, continuing to just use the bind group index as the descriptor set value. The Naga backend looks up each resource in the map when emitting its `Binding` and `DescriptorSet` decorations. If the entry cannot be found in the map, it will either error or emit fake bindings based on its configuration.
1 parent 0337f1e commit 27f32dc

File tree

8 files changed

+96
-42
lines changed

8 files changed

+96
-42
lines changed

naga-test/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,7 @@ impl SpirvOutParameters {
155155
Some(self.capabilities.clone())
156156
},
157157
bounds_check_policies,
158+
fake_missing_bindings: true,
158159
binding_map: self.binding_map.clone(),
159160
zero_initialize_workgroup_memory: spv::ZeroInitializeWorkgroupMemoryMode::Polyfill,
160161
force_loop_bounding: true,

naga/src/back/spv/mod.rs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,8 @@ pub enum Error {
7979
Override,
8080
#[error(transparent)]
8181
ResolveArraySizeError(#[from] crate::proc::ResolveArraySizeError),
82+
#[error("mapping of {0:?} is missing")]
83+
MissingBinding(crate::ResourceBinding),
8284
}
8385

8486
#[derive(Default)]
@@ -760,6 +762,7 @@ pub struct Writer {
760762
constant_ids: HandleVec<crate::Expression, Word>,
761763
cached_constants: crate::FastHashMap<CachedConstant, Word>,
762764
global_variables: HandleVec<crate::GlobalVariable, GlobalVariable>,
765+
fake_missing_bindings: bool,
763766
binding_map: BindingMap,
764767

765768
// Cached expressions are only meaningful within a BlockContext, but we
@@ -811,10 +814,12 @@ bitflags::bitflags! {
811814
}
812815
}
813816

814-
#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)]
817+
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Hash)]
815818
#[cfg_attr(feature = "serialize", derive(serde::Serialize))]
816819
#[cfg_attr(feature = "deserialize", derive(serde::Deserialize))]
817820
pub struct BindingInfo {
821+
pub descriptor_set: u32,
822+
pub binding: u32,
818823
/// If the binding is an unsized binding array, this overrides the size.
819824
pub binding_array_size: Option<u32>,
820825
}
@@ -839,6 +844,10 @@ pub struct Options<'a> {
839844
/// Configuration flags for the writer.
840845
pub flags: WriterFlags,
841846

847+
/// Don't panic on missing bindings. Instead use fake values for `Binding`
848+
/// and `DescriptorSet` decorations. This may result in invalid SPIR-V.
849+
pub fake_missing_bindings: bool,
850+
842851
/// Map of resources to information about the binding.
843852
pub binding_map: BindingMap,
844853

@@ -877,6 +886,7 @@ impl Default for Options<'_> {
877886
Options {
878887
lang_version: (1, 0),
879888
flags,
889+
fake_missing_bindings: true,
880890
binding_map: BindingMap::default(),
881891
capabilities: None,
882892
bounds_check_policies: BoundsCheckPolicies::default(),

naga/src/back/spv/writer.rs

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ impl Writer {
8787
constant_ids: HandleVec::new(),
8888
cached_constants: crate::FastHashMap::default(),
8989
global_variables: HandleVec::new(),
90+
fake_missing_bindings: options.fake_missing_bindings,
9091
binding_map: options.binding_map.clone(),
9192
saved_cached: CachedExpressions::default(),
9293
gl450_ext_inst_id,
@@ -149,6 +150,7 @@ impl Writer {
149150
force_loop_bounding: self.force_loop_bounding,
150151
use_storage_input_output_16: self.use_storage_input_output_16,
151152
capabilities_available: take(&mut self.capabilities_available),
153+
fake_missing_bindings: self.fake_missing_bindings,
152154
binding_map: take(&mut self.binding_map),
153155

154156
// Initialized afresh:
@@ -469,6 +471,26 @@ impl Writer {
469471
})
470472
}
471473

474+
/// Resolve the [`BindingInfo`] for a [`crate::ResourceBinding`] from the
475+
/// provided [`Writer::binding_map`].
476+
///
477+
/// If the specified resource is not present in the binding map this will
478+
/// return an error, unless [`Writer::fake_missing_bindings`] is set.
479+
fn resolve_resource_binding(
480+
&self,
481+
res_binding: &crate::ResourceBinding,
482+
) -> Result<BindingInfo, Error> {
483+
match self.binding_map.get(res_binding) {
484+
Some(target) => Ok(*target),
485+
None if self.fake_missing_bindings => Ok(BindingInfo {
486+
descriptor_set: res_binding.group,
487+
binding: res_binding.binding,
488+
binding_array_size: None,
489+
}),
490+
None => Err(Error::MissingBinding(*res_binding)),
491+
}
492+
}
493+
472494
/// Emits code for any wrapper functions required by the expressions in ir_function.
473495
/// The IDs of any emitted functions will be stored in [`Self::wrapped_functions`].
474496
fn write_wrapped_functions(
@@ -2241,13 +2263,11 @@ impl Writer {
22412263
// and it is failing on 0.
22422264
let mut substitute_inner_type_lookup = None;
22432265
if let Some(ref res_binding) = global_variable.binding {
2244-
self.decorate(id, Decoration::DescriptorSet, &[res_binding.group]);
2245-
self.decorate(id, Decoration::Binding, &[res_binding.binding]);
2266+
let bind_target = self.resolve_resource_binding(res_binding)?;
2267+
self.decorate(id, Decoration::DescriptorSet, &[bind_target.descriptor_set]);
2268+
self.decorate(id, Decoration::Binding, &[bind_target.binding]);
22462269

2247-
if let Some(&BindingInfo {
2248-
binding_array_size: Some(remapped_binding_array_size),
2249-
}) = self.binding_map.get(res_binding)
2250-
{
2270+
if let Some(remapped_binding_array_size) = bind_target.binding_array_size {
22512271
if let crate::TypeInner::BindingArray { base, .. } =
22522272
ir_module.types[global_variable.ty].inner
22532273
{

naga/tests/in/wgsl/binding-arrays.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,5 +62,5 @@ resource_binding = { group = 0, binding = 8 }
6262
version = [1, 1]
6363

6464
[[spv.binding_map]]
65-
bind_target = { binding_array_size = 10 }
65+
bind_target = { descriptor_set = 0, binding = 0, binding_array_size = 10 }
6666
resource_binding = { group = 0, binding = 0 }

naga/tests/in/wgsl/binding-buffer-arrays.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,5 +9,5 @@ image = "ReadZeroSkipWrite"
99
version = [1, 1]
1010

1111
[[spv.binding_map]]
12-
bind_target = { binding_array_size = 10 }
12+
bind_target = { descriptor_set = 0, binding = 0, binding_array_size = 10 }
1313
resource_binding = { group = 0, binding = 0 }

wgpu-hal/src/vulkan/adapter.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2166,6 +2166,7 @@ impl super::Adapter {
21662166
force_loop_bounding: true,
21672167
use_storage_input_output_16: features.contains(wgt::Features::SHADER_F16)
21682168
&& self.phd_features.supports_storage_input_output_16(),
2169+
fake_missing_bindings: false,
21692170
// We need to build this separately for each invocation, so just default it out here
21702171
binding_map: BTreeMap::default(),
21712172
debug_info: None,

wgpu-hal/src/vulkan/device.rs

Lines changed: 41 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1482,16 +1482,17 @@ impl crate::Device for super::Device {
14821482
) -> Result<super::BindGroupLayout, crate::DeviceError> {
14831483
// Iterate through the entries and accumulate our Vulkan
14841484
// DescriptorSetLayoutBindings and DescriptorBindingFlags, as well as
1485-
// the list of which bindings are binding arrays, and our descriptor
1486-
// counts.
1485+
// our binding map and our descriptor counts.
14871486
// Note: not bothering with on stack arrays here as it's low frequency
14881487
let mut vk_bindings = Vec::new();
14891488
let mut binding_flags = Vec::new();
1490-
let mut binding_arrays = Vec::new();
1489+
let mut binding_map = Vec::new();
1490+
let mut next_binding = 0;
1491+
let mut contains_binding_arrays = false;
14911492
let mut desc_count = gpu_descriptor::DescriptorTotalCount::default();
1492-
for (i, entry) in desc.entries.iter().enumerate() {
1493-
if let Some(count) = entry.count {
1494-
binding_arrays.push((i as u32, count))
1493+
for entry in desc.entries {
1494+
if entry.count.is_some() {
1495+
contains_binding_arrays = true;
14951496
}
14961497

14971498
let partially_bound = desc
@@ -1510,14 +1511,22 @@ impl crate::Device for super::Device {
15101511
wgt::BindingType::ExternalTexture => unimplemented!(),
15111512
_ => {
15121513
vk_bindings.push(vk::DescriptorSetLayoutBinding {
1513-
binding: entry.binding,
1514+
binding: next_binding,
15141515
descriptor_type: conv::map_binding_type(entry.ty),
15151516
descriptor_count: count,
15161517
stage_flags: conv::map_shader_stage(entry.visibility),
15171518
p_immutable_samplers: ptr::null(),
15181519
_marker: Default::default(),
15191520
});
15201521
binding_flags.push(flags);
1522+
binding_map.push((
1523+
entry.binding,
1524+
super::BindingInfo {
1525+
binding: next_binding,
1526+
binding_array_size: entry.count,
1527+
},
1528+
));
1529+
next_binding += 1;
15211530
}
15221531
}
15231532

@@ -1560,7 +1569,7 @@ impl crate::Device for super::Device {
15601569

15611570
let vk_info = vk::DescriptorSetLayoutCreateInfo::default()
15621571
.bindings(&vk_bindings)
1563-
.flags(if !binding_arrays.is_empty() {
1572+
.flags(if contains_binding_arrays {
15641573
vk::DescriptorSetLayoutCreateFlags::UPDATE_AFTER_BIND_POOL
15651574
} else {
15661575
vk::DescriptorSetLayoutCreateFlags::empty()
@@ -1588,7 +1597,8 @@ impl crate::Device for super::Device {
15881597
raw,
15891598
desc_count,
15901599
entries: desc.entries.into(),
1591-
binding_arrays,
1600+
binding_map,
1601+
contains_binding_arrays,
15921602
})
15931603
}
15941604
unsafe fn destroy_bind_group_layout(&self, bg_layout: super::BindGroupLayout) {
@@ -1640,27 +1650,25 @@ impl crate::Device for super::Device {
16401650
unsafe { self.shared.set_object_name(raw, label) };
16411651
}
16421652

1643-
let mut binding_arrays = BTreeMap::new();
1653+
let mut binding_map = BTreeMap::new();
16441654
for (group, &layout) in desc.bind_group_layouts.iter().enumerate() {
1645-
for &(binding, binding_array_size) in &layout.binding_arrays {
1646-
binding_arrays.insert(
1655+
for &(binding, binding_info) in &layout.binding_map {
1656+
binding_map.insert(
16471657
naga::ResourceBinding {
16481658
group: group as u32,
16491659
binding,
16501660
},
16511661
naga::back::spv::BindingInfo {
1652-
binding_array_size: Some(binding_array_size.get()),
1662+
descriptor_set: group as u32,
1663+
binding: binding_info.binding,
1664+
binding_array_size: binding_info.binding_array_size.map(NonZeroU32::get),
16531665
},
16541666
);
16551667
}
16561668
}
16571669

16581670
self.counters.pipeline_layouts.add(1);
1659-
1660-
Ok(super::PipelineLayout {
1661-
raw,
1662-
binding_arrays,
1663-
})
1671+
Ok(super::PipelineLayout { raw, binding_map })
16641672
}
16651673
unsafe fn destroy_pipeline_layout(&self, pipeline_layout: super::PipelineLayout) {
16661674
unsafe {
@@ -1682,9 +1690,7 @@ impl crate::Device for super::Device {
16821690
super::AccelerationStructure,
16831691
>,
16841692
) -> Result<super::BindGroup, crate::DeviceError> {
1685-
let contains_binding_arrays = !desc.layout.binding_arrays.is_empty();
1686-
1687-
let desc_set_layout_flags = if contains_binding_arrays {
1693+
let desc_set_layout_flags = if desc.layout.contains_binding_arrays {
16881694
gpu_descriptor::DescriptorSetLayoutCreateFlags::UPDATE_AFTER_BIND
16891695
} else {
16901696
gpu_descriptor::DescriptorSetLayoutCreateFlags::empty()
@@ -1780,6 +1786,7 @@ impl crate::Device for super::Device {
17801786
.expect("internal error: no layout entry found with binding slot");
17811787
(layout, entry)
17821788
});
1789+
let mut next_binding = 0;
17831790
for (layout, entry) in layout_and_entry_iter {
17841791
let write = vk::WriteDescriptorSet::default().dst_set(*set.raw());
17851792

@@ -1794,10 +1801,11 @@ impl crate::Device for super::Device {
17941801
));
17951802
writes.push(
17961803
write
1797-
.dst_binding(entry.binding)
1804+
.dst_binding(next_binding)
17981805
.descriptor_type(conv::map_binding_type(layout.ty))
17991806
.image_info(local_image_infos),
18001807
);
1808+
next_binding += 1;
18011809
}
18021810
wgt::BindingType::Texture { .. } | wgt::BindingType::StorageTexture { .. } => {
18031811
let start = entry.resource_index;
@@ -1815,10 +1823,11 @@ impl crate::Device for super::Device {
18151823
));
18161824
writes.push(
18171825
write
1818-
.dst_binding(entry.binding)
1826+
.dst_binding(next_binding)
18191827
.descriptor_type(conv::map_binding_type(layout.ty))
18201828
.image_info(local_image_infos),
18211829
);
1830+
next_binding += 1;
18221831
}
18231832
wgt::BindingType::Buffer { .. } => {
18241833
let start = entry.resource_index;
@@ -1837,10 +1846,11 @@ impl crate::Device for super::Device {
18371846
));
18381847
writes.push(
18391848
write
1840-
.dst_binding(entry.binding)
1849+
.dst_binding(next_binding)
18411850
.descriptor_type(conv::map_binding_type(layout.ty))
18421851
.buffer_info(local_buffer_infos),
18431852
);
1853+
next_binding += 1;
18441854
}
18451855
wgt::BindingType::AccelerationStructure { .. } => {
18461856
let start = entry.resource_index;
@@ -1867,11 +1877,12 @@ impl crate::Device for super::Device {
18671877

18681878
writes.push(
18691879
write
1870-
.dst_binding(entry.binding)
1880+
.dst_binding(next_binding)
18711881
.descriptor_type(conv::map_binding_type(layout.ty))
18721882
.descriptor_count(entry.count)
18731883
.push_next(local_acceleration_structure_infos),
18741884
);
1885+
next_binding += 1;
18751886
}
18761887
wgt::BindingType::ExternalTexture => unimplemented!(),
18771888
}
@@ -2033,7 +2044,7 @@ impl crate::Device for super::Device {
20332044
compiled_vs = Some(self.compile_stage(
20342045
vertex_stage,
20352046
naga::ShaderStage::Vertex,
2036-
&desc.layout.binding_arrays,
2047+
&desc.layout.binding_map,
20372048
)?);
20382049
stages.push(compiled_vs.as_ref().unwrap().create_info);
20392050
}
@@ -2045,14 +2056,14 @@ impl crate::Device for super::Device {
20452056
compiled_ts = Some(self.compile_stage(
20462057
t,
20472058
naga::ShaderStage::Task,
2048-
&desc.layout.binding_arrays,
2059+
&desc.layout.binding_map,
20492060
)?);
20502061
stages.push(compiled_ts.as_ref().unwrap().create_info);
20512062
}
20522063
compiled_ms = Some(self.compile_stage(
20532064
mesh_stage,
20542065
naga::ShaderStage::Mesh,
2055-
&desc.layout.binding_arrays,
2066+
&desc.layout.binding_map,
20562067
)?);
20572068
stages.push(compiled_ms.as_ref().unwrap().create_info);
20582069
}
@@ -2062,7 +2073,7 @@ impl crate::Device for super::Device {
20622073
let compiled = self.compile_stage(
20632074
stage,
20642075
naga::ShaderStage::Fragment,
2065-
&desc.layout.binding_arrays,
2076+
&desc.layout.binding_map,
20662077
)?;
20672078
stages.push(compiled.create_info);
20682079
Some(compiled)
@@ -2270,7 +2281,7 @@ impl crate::Device for super::Device {
22702281
let compiled = self.compile_stage(
22712282
&desc.stage,
22722283
naga::ShaderStage::Compute,
2273-
&desc.layout.binding_arrays,
2284+
&desc.layout.binding_map,
22742285
)?;
22752286

22762287
let vk_infos = [{

wgpu-hal/src/vulkan/mod.rs

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1001,22 +1001,33 @@ pub struct Sampler {
10011001

10021002
impl crate::DynSampler for Sampler {}
10031003

1004+
/// Information about a binding within a specific BindGroupLayout / BindGroup.
1005+
/// This will be used to construct a [`naga::back::spv::BindingInfo`], where
1006+
/// the descriptor set value will be taken from the index of the group.
1007+
#[derive(Copy, Clone, Debug)]
1008+
struct BindingInfo {
1009+
binding: u32,
1010+
binding_array_size: Option<NonZeroU32>,
1011+
}
1012+
10041013
#[derive(Debug)]
10051014
pub struct BindGroupLayout {
10061015
raw: vk::DescriptorSetLayout,
10071016
desc_count: gpu_descriptor::DescriptorTotalCount,
10081017
/// Sorted list of entries.
10091018
entries: Box<[wgt::BindGroupLayoutEntry]>,
1010-
/// Map of binding index to size,
1011-
binding_arrays: Vec<(u32, NonZeroU32)>,
1019+
/// Map of original binding index to remapped binding index and optional
1020+
/// array size.
1021+
binding_map: Vec<(u32, BindingInfo)>,
1022+
contains_binding_arrays: bool,
10121023
}
10131024

10141025
impl crate::DynBindGroupLayout for BindGroupLayout {}
10151026

10161027
#[derive(Debug)]
10171028
pub struct PipelineLayout {
10181029
raw: vk::PipelineLayout,
1019-
binding_arrays: naga::back::spv::BindingMap,
1030+
binding_map: naga::back::spv::BindingMap,
10201031
}
10211032

10221033
impl crate::DynPipelineLayout for PipelineLayout {}

0 commit comments

Comments
 (0)