From b3a3dc33b6e801d7409abf2fba3bef5d0bc4f802 Mon Sep 17 00:00:00 2001 From: Jamie Nicol Date: Mon, 2 Jun 2025 17:21:23 +0100 Subject: [PATCH] [dx12] Support external textures on DX12 This implements the DX12 HAL part of external texture support, which is the final piece of the puzzle for external textures on DirectX 12. When creating a pipeline layout, HAL is responsible for mapping a single BindingType::ExternalTexture bind group entry to multiple descriptor ranges in the root signature it creates: 3 SRVs (one for each texture plane) and a CBV for the parameters buffer. Additionally we must expose the additional bindings to the Naga backend via the `external_texture_binding_map`. Lastly, when creating a bind group we write the descriptors for each of these bindings to the heap. And with that, we can finally enable the `EXTERNAL_TEXTURE` feature for the dx12 backend. --- wgpu-hal/src/dx12/adapter.rs | 3 +- wgpu-hal/src/dx12/conv.rs | 5 +- wgpu-hal/src/dx12/device.rs | 187 ++++++++++++++++++++++++----------- wgpu-types/src/features.rs | 2 +- 4 files changed, 139 insertions(+), 58 deletions(-) diff --git a/wgpu-hal/src/dx12/adapter.rs b/wgpu-hal/src/dx12/adapter.rs index 22d52be4d7..3a0ece692f 100644 --- a/wgpu-hal/src/dx12/adapter.rs +++ b/wgpu-hal/src/dx12/adapter.rs @@ -361,7 +361,8 @@ impl super::Adapter { | wgt::Features::DUAL_SOURCE_BLENDING | wgt::Features::TEXTURE_FORMAT_NV12 | wgt::Features::FLOAT32_FILTERABLE - | wgt::Features::TEXTURE_ATOMIC; + | wgt::Features::TEXTURE_ATOMIC + | wgt::Features::EXTERNAL_TEXTURE; //TODO: in order to expose this, we need to run a compute shader // that extract the necessary statistics out of the D3D12 result. diff --git a/wgpu-hal/src/dx12/conv.rs b/wgpu-hal/src/dx12/conv.rs index 2ccb96dd84..6005f43bf1 100644 --- a/wgpu-hal/src/dx12/conv.rs +++ b/wgpu-hal/src/dx12/conv.rs @@ -135,7 +135,10 @@ pub fn map_binding_type(ty: &wgt::BindingType) -> Direct3D12::D3D12_DESCRIPTOR_R } | Bt::StorageTexture { .. } => Direct3D12::D3D12_DESCRIPTOR_RANGE_TYPE_UAV, Bt::AccelerationStructure { .. } => Direct3D12::D3D12_DESCRIPTOR_RANGE_TYPE_SRV, - Bt::ExternalTexture => unimplemented!(), + // External textures require multiple bindings and therefore cannot + // be mapped to a single descriptor range type. They must be handled + // separately by the caller. + Bt::ExternalTexture => unreachable!("External textures must be handled separately"), } } diff --git a/wgpu-hal/src/dx12/device.rs b/wgpu-hal/src/dx12/device.rs index d8bdd2b1d5..8eb4bb8864 100644 --- a/wgpu-hal/src/dx12/device.rs +++ b/wgpu-hal/src/dx12/device.rs @@ -807,7 +807,8 @@ impl crate::Device for super::Device { | wgt::BindingType::StorageTexture { .. } | wgt::BindingType::AccelerationStructure { .. } => num_views += count, wgt::BindingType::Sampler { .. } => has_sampler_in_group = true, - wgt::BindingType::ExternalTexture => unimplemented!(), + // Three texture planes and one params buffer + wgt::BindingType::ExternalTexture => num_views += 4 * count, } } @@ -880,6 +881,7 @@ impl crate::Device for super::Device { let mut binding_map = hlsl::BindingMap::default(); let mut sampler_buffer_binding_map = hlsl::SamplerIndexBufferBindingMap::default(); + let mut external_texture_binding_map = hlsl::ExternalTextureBindingMap::default(); let mut bind_cbv = hlsl::BindTarget::default(); let mut bind_srv = hlsl::BindTarget::default(); let mut bind_uav = hlsl::BindTarget::default(); @@ -939,6 +941,8 @@ impl crate::Device for super::Device { .. } => {} wgt::BindingType::Sampler(_) => sampler_in_bind_group = true, + // Three texture planes and one params buffer + wgt::BindingType::ExternalTexture => total_non_dynamic_entries += 4, _ => total_non_dynamic_entries += 1, } } @@ -993,61 +997,110 @@ impl crate::Device for super::Device { // SRV/CBV/UAV descriptor tables let range_base = ranges.len(); for entry in bgl.entries.iter() { - let (range_ty, has_dynamic_offset) = match entry.ty { - wgt::BindingType::Buffer { - ty, - has_dynamic_offset: true, - .. - } => match ty { - wgt::BufferBindingType::Uniform => continue, - wgt::BufferBindingType::Storage { .. } => { - (conv::map_binding_type(&entry.ty), true) - } - }, - ref other => (conv::map_binding_type(other), false), - }; - let bt = match range_ty { - Direct3D12::D3D12_DESCRIPTOR_RANGE_TYPE_CBV => &mut bind_cbv, - Direct3D12::D3D12_DESCRIPTOR_RANGE_TYPE_SRV => &mut bind_srv, - Direct3D12::D3D12_DESCRIPTOR_RANGE_TYPE_UAV => &mut bind_uav, - Direct3D12::D3D12_DESCRIPTOR_RANGE_TYPE_SAMPLER => continue, - _ => todo!(), - }; - - let binding_array_size = entry.count.map(NonZeroU32::get); - - let dynamic_storage_buffer_offsets_index = if has_dynamic_offset { - debug_assert!( - binding_array_size.is_none(), - "binding arrays and dynamic buffers are mutually exclusive" + let count = entry.count.map_or(1, NonZeroU32::get); + if let wgt::BindingType::ExternalTexture = entry.ty { + // External textures need 3 SRVs (a texture for each plane) + // and 1 CBV for the parameters buffer. + let bind_target = hlsl::ExternalTextureBindTarget { + planes: core::array::from_fn(|_| hlsl::BindTarget { + register: { + let register = bind_srv.register; + bind_srv.register += count; + register + }, + ..bind_srv + }), + params: hlsl::BindTarget { + register: { + let register = bind_cbv.register; + bind_cbv.register += count; + register + }, + ..bind_cbv + }, + }; + external_texture_binding_map.insert( + naga::ResourceBinding { + group: index as u32, + binding: entry.binding, + }, + bind_target, ); - let ret = Some(dynamic_storage_buffers); - dynamic_storage_buffers += 1; - ret + for bt in bind_target.planes { + ranges.push(Direct3D12::D3D12_DESCRIPTOR_RANGE { + RangeType: Direct3D12::D3D12_DESCRIPTOR_RANGE_TYPE_SRV, + NumDescriptors: count, + BaseShaderRegister: bt.register, + RegisterSpace: bt.space as u32, + OffsetInDescriptorsFromTableStart: + Direct3D12::D3D12_DESCRIPTOR_RANGE_OFFSET_APPEND, + }); + } + ranges.push(Direct3D12::D3D12_DESCRIPTOR_RANGE { + RangeType: Direct3D12::D3D12_DESCRIPTOR_RANGE_TYPE_CBV, + NumDescriptors: count, + BaseShaderRegister: bind_target.params.register, + RegisterSpace: bind_target.params.space as u32, + OffsetInDescriptorsFromTableStart: + Direct3D12::D3D12_DESCRIPTOR_RANGE_OFFSET_APPEND, + }); } else { - None - }; + let (range_ty, has_dynamic_offset) = match entry.ty { + wgt::BindingType::Buffer { + ty, + has_dynamic_offset: true, + .. + } => match ty { + wgt::BufferBindingType::Uniform => continue, + wgt::BufferBindingType::Storage { .. } => { + (conv::map_binding_type(&entry.ty), true) + } + }, + ref other => (conv::map_binding_type(other), false), + }; + let bt = match range_ty { + Direct3D12::D3D12_DESCRIPTOR_RANGE_TYPE_CBV => &mut bind_cbv, + Direct3D12::D3D12_DESCRIPTOR_RANGE_TYPE_SRV => &mut bind_srv, + Direct3D12::D3D12_DESCRIPTOR_RANGE_TYPE_UAV => &mut bind_uav, + Direct3D12::D3D12_DESCRIPTOR_RANGE_TYPE_SAMPLER => continue, + _ => todo!(), + }; - binding_map.insert( - naga::ResourceBinding { - group: index as u32, - binding: entry.binding, - }, - hlsl::BindTarget { - binding_array_size, - dynamic_storage_buffer_offsets_index, - ..*bt - }, - ); - ranges.push(Direct3D12::D3D12_DESCRIPTOR_RANGE { - RangeType: range_ty, - NumDescriptors: entry.count.map_or(1, |count| count.get()), - BaseShaderRegister: bt.register, - RegisterSpace: bt.space as u32, - OffsetInDescriptorsFromTableStart: - Direct3D12::D3D12_DESCRIPTOR_RANGE_OFFSET_APPEND, - }); - bt.register += entry.count.map(NonZeroU32::get).unwrap_or(1); + let binding_array_size = entry.count.map(NonZeroU32::get); + + let dynamic_storage_buffer_offsets_index = if has_dynamic_offset { + debug_assert!( + binding_array_size.is_none(), + "binding arrays and dynamic buffers are mutually exclusive" + ); + let ret = Some(dynamic_storage_buffers); + dynamic_storage_buffers += 1; + ret + } else { + None + }; + + binding_map.insert( + naga::ResourceBinding { + group: index as u32, + binding: entry.binding, + }, + hlsl::BindTarget { + binding_array_size, + dynamic_storage_buffer_offsets_index, + ..*bt + }, + ); + ranges.push(Direct3D12::D3D12_DESCRIPTOR_RANGE { + RangeType: range_ty, + NumDescriptors: count, + BaseShaderRegister: bt.register, + RegisterSpace: bt.space as u32, + OffsetInDescriptorsFromTableStart: + Direct3D12::D3D12_DESCRIPTOR_RANGE_OFFSET_APPEND, + }); + bt.register += count; + } } let mut sampler_index_within_bind_group = 0; @@ -1388,7 +1441,7 @@ impl crate::Device for super::Device { restrict_indexing: true, sampler_heap_target, sampler_buffer_binding_map, - external_texture_binding_map: hlsl::ExternalTextureBindingMap::default(), + external_texture_binding_map, force_loop_bounding: true, }, }) @@ -1574,7 +1627,31 @@ impl crate::Device for super::Device { inner.stage.push(handle); } } - wgt::BindingType::ExternalTexture => unimplemented!(), + wgt::BindingType::ExternalTexture => { + // We don't yet support binding arrays of external textures. + // https://github.com/gfx-rs/wgpu/issues/8027 + assert_eq!(entry.count, 1); + let external_texture = &desc.external_textures[entry.resource_index as usize]; + for plane in &external_texture.planes { + let plane_handle = plane.view.handle_srv.unwrap(); + cpu_views.as_mut().unwrap().stage.push(plane_handle.raw); + } + let gpu_address = external_texture.params.resolve_address(); + let size = external_texture.params.resolve_size() as u32; + let inner = cpu_views.as_mut().unwrap(); + let cpu_index = inner.stage.len() as u32; + let params_handle = desc.layout.cpu_heap_views.as_ref().unwrap().at(cpu_index); + let size_mask = Direct3D12::D3D12_CONSTANT_BUFFER_DATA_PLACEMENT_ALIGNMENT - 1; + let raw_desc = Direct3D12::D3D12_CONSTANT_BUFFER_VIEW_DESC { + BufferLocation: gpu_address, + SizeInBytes: ((size - 1) | size_mask) + 1, + }; + unsafe { + self.raw + .CreateConstantBufferView(Some(&raw_desc), params_handle) + }; + inner.stage.push(params_handle); + } } } diff --git a/wgpu-types/src/features.rs b/wgpu-types/src/features.rs index 5880c6ff35..908d5bda20 100644 --- a/wgpu-types/src/features.rs +++ b/wgpu-types/src/features.rs @@ -997,7 +997,7 @@ bitflags_array! { /// all. /// /// Supported platforms: - /// - None + /// - DX12 const EXTERNAL_TEXTURE = 1 << 30; // Shader: