Fixed partially-bound texture and storage-texture binding arrays (PAR…#9653
Fixed partially-bound texture and storage-texture binding arrays (PAR…#9653holg wants to merge 4 commits into
Conversation
…TIALLY_BOUND_BINDING_ARRAY) reading garbage in create_bind_group.
There was a problem hiding this comment.
Pull request overview
Note
Copilot was unable to run its full agentic suite in this review.
This PR fixes DX12 bind-group descriptor-table alignment when a binding array is only partially populated, preventing subsequent bindings in the same descriptor table from being shifted and read from the wrong slot.
Changes:
- Pad partially-bound texture and storage-texture binding arrays with null descriptors on DX12 to keep later bindings aligned.
- Add a GPU regression test where a partially-bound texture array is followed by another binding in the same bind group.
- Document the fix in the changelog.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 9 comments.
| File | Description |
|---|---|
| wgpu-hal/src/dx12/device.rs | Adds DX12 staging-time padding of descriptor tables for partially-bound texture and storage-texture arrays. |
| tests/tests/wgpu-gpu/binding_array/sampled_textures.rs | Adds a regression GPU test covering “partial binding array followed by another binding” layout. |
| CHANGELOG.md | Notes the bugfix for partially-bound binding arrays on DX12. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| // The table range reserves the full `layout.count`, so pad a | ||
| // partially-bound array with null SRVs to keep later bindings aligned. | ||
| let array_size = layout.count.map_or(1, NonZeroU32::get); | ||
| for _ in entry.count..array_size { | ||
| let inner = cpu_views.as_mut().unwrap(); | ||
| let cpu_index = inner.stage.len() as u32; | ||
| let handle = desc.layout.cpu_heap_views.as_ref().unwrap().at(cpu_index); | ||
| let raw_desc = Direct3D12::D3D12_SHADER_RESOURCE_VIEW_DESC { | ||
| Format: Dxgi::Common::DXGI_FORMAT_R8G8B8A8_UNORM, | ||
| ViewDimension: Direct3D12::D3D12_SRV_DIMENSION_TEXTURE2D, | ||
| Shader4ComponentMapping: | ||
| Direct3D12::D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING, | ||
| Anonymous: Direct3D12::D3D12_SHADER_RESOURCE_VIEW_DESC_0 { | ||
| Texture2D: Direct3D12::D3D12_TEX2D_SRV { | ||
| MostDetailedMip: 0, | ||
| MipLevels: 1, | ||
| PlaneSlice: 0, | ||
| ResourceMinLODClamp: 0.0, | ||
| }, | ||
| }, | ||
| }; |
There was a problem hiding this comment.
The feature only lets you bind fewer elements than the layout declares; it doesn't change out-of-range access semantics
| // Same partial-binding-array padding as the `Texture` branch above, but | ||
| // with null UAVs so that bindings following the array keep their offsets. | ||
| let array_size = layout.count.map_or(1, NonZeroU32::get); | ||
| for _ in entry.count..array_size { | ||
| let inner = cpu_views.as_mut().unwrap(); | ||
| let cpu_index = inner.stage.len() as u32; | ||
| let handle = desc.layout.cpu_heap_views.as_ref().unwrap().at(cpu_index); | ||
| let raw_desc = Direct3D12::D3D12_UNORDERED_ACCESS_VIEW_DESC { | ||
| Format: Dxgi::Common::DXGI_FORMAT_R8G8B8A8_UNORM, | ||
| ViewDimension: Direct3D12::D3D12_UAV_DIMENSION_TEXTURE2D, | ||
| Anonymous: Direct3D12::D3D12_UNORDERED_ACCESS_VIEW_DESC_0 { | ||
| Texture2D: Direct3D12::D3D12_TEX2D_UAV { | ||
| MipSlice: 0, | ||
| PlaneSlice: 0, | ||
| }, | ||
| }, | ||
| }; |
There was a problem hiding this comment.
The feature only lets you bind fewer elements than the layout declares; it doesn't change out-of-range access semantics
| // The table range reserves the full `layout.count`, so pad a | ||
| // partially-bound array with null SRVs to keep later bindings aligned. | ||
| let array_size = layout.count.map_or(1, NonZeroU32::get); | ||
| for _ in entry.count..array_size { |
| for _ in entry.count..array_size { | ||
| let inner = cpu_views.as_mut().unwrap(); | ||
| let cpu_index = inner.stage.len() as u32; | ||
| let handle = desc.layout.cpu_heap_views.as_ref().unwrap().at(cpu_index); |
| unsafe { | ||
| self.raw | ||
| .CreateShaderResourceView(None, Some(&raw_desc), handle) | ||
| }; | ||
| inner.stage.push(handle); | ||
| } |
There was a problem hiding this comment.
Out of scope for this regression fix. Note there's no lock contention here: cpu_heap_views.at() indexes into the bind group's own per-creation CPU heap, which isn't shared
| } | ||
|
|
||
| #[gpu_test] | ||
| static PARTIAL_BINDING_ARRAY_FOLLOWED_BY_UNIFORM: GpuTestConfiguration = |
| /// Regression test for a DX12 descriptor-table bug. A *partially-bound* binding | ||
| /// array (binding 0) is followed by another binding (binding 1, a uniform) in the |
| @group(0) @binding(1) | ||
| var<storage, read> marker: vec4<f32>; |
| - Fixed stencil values read with `textureLoad` appearing in G instead of R. By @andyleiserson in [#9520](https://github.com/gfx-rs/wgpu/pull/9520). | ||
| - Fixed some cases where the `textureNum{Layers,Levels,Samples}` functions returned incorrect results. By @andyleiserson in [#9542](https://github.com/gfx-rs/wgpu/pull/9542). | ||
| - Fixed `map_texture_format_for_copy` panicking on `(planar_format, single_plane_aspect)` during buffer<->texture transfers, and `TextureView::subresource_index` previously being hard-coded to plane 0. By @AdrianEddy in [#9551](https://github.com/gfx-rs/wgpu/pull/9551). | ||
| - Fixed partially-bound texture and storage-texture binding arrays (`PARTIALLY_BOUND_BINDING_ARRAY`) reading garbage in `create_bind_group`. By @holg in [#NNNN](https://github.com/gfx-rs/wgpu/pull/NNNN). |
There was a problem hiding this comment.
resolved, and kind of silly to note this, obviously we need to create the PR first, before having the #NNNN
Fixed partially-bound texture and storage-texture binding arrays (
PARTIALLY_BOUND_BINDING_ARRAY) reading garbage increate_bind_group.Connections
Related to #3637 (bindless tracking)
When one pull request builds on another, please put "Depends on
#NNNN" towards the top of its description. This helps maintainers
notice that they shouldn't merge it until its ancestor has been
approved. Don't use draft PR status to indicate this.
Description
Fixed partially-bound texture and storage-texture binding arrays (
PARTIALLY_BOUND_BINDING_ARRAY) reading garbage increate_bind_groupTesting
Added test case in tests/tests/wgpu-gpu/binding_array/sampled_textures.rs
Squash or Rebase?
If your pull request contains multiple commits, please indicate whether
they need to be squashed into a single commit before they're merged,
or if they're ready to rebase onto
trunkas they stand. In thelatter case, please ensure that each commit passes all CI tests, so
that we can continue to bisect along
trunkto isolate bugs.Checklist
wgpumay be affected behaviorally.CHANGELOG.mdentries for the user-facing effects of this change are present.