[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.
This commit is contained in:
Jamie Nicol 2025-06-02 17:21:23 +01:00 committed by Teodor Tanasoaia
parent b5385225ab
commit b3a3dc33b6
4 changed files with 139 additions and 58 deletions

View File

@ -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.

View File

@ -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"),
}
}

View File

@ -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);
}
}
}

View File

@ -997,7 +997,7 @@ bitflags_array! {
/// all.
///
/// Supported platforms:
/// - None
/// - DX12
const EXTERNAL_TEXTURE = 1 << 30;
// Shader: