Skip to content

Commit 2d2cc85

Browse files
authored
[SYCL][Bindless] Add external_mem_handle_type::dma_buf (#18988)
* Add support for importing `dma_buf` handles into SYCL * Add `supports_importing_handle_type` that checks whether a device supports importing an external memory handle type. This is because `dma_buf` is only supported on L0 backend for now. * Updated Bindless Images extension document * Updated "Example 6" E2E test to use `dma_buf` handle * Updated `buffer_usm` Vulkan interop E2E test to use `dma_buf` handle if the both the Vulkan driver and the device backend supports it * Added overload of `get_ur_handles` that only takes a SYCL device
1 parent 664f30e commit 2d2cc85

34 files changed

+652
-74
lines changed

sycl/doc/extensions/experimental/sycl_ext_oneapi_bindless_images.asciidoc

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2074,6 +2074,7 @@ enum class external_mem_handle_type {
20742074
opaque_fd = 0,
20752075
win32_nt_handle = 1,
20762076
win32_nt_dx12_resource = 2,
2077+
dma_buf = 3,
20772078
};
20782079
20792080
// Descriptor templated on specific resource type
@@ -2095,10 +2096,14 @@ for Windows NT resource handles.
20952096
The user must populate the `external_mem_descriptor` with the appropriate
20962097
`ResourceType` values, a `handle_type`, and the size of the external memory in
20972098
bytes, before they can then import that memory into SYCL through
2098-
`import_external_memory`. Note that some handle types can only be used in
2099-
combination with certain resource types, for example the `opaque_fd` handle type
2100-
is only used on Linux systems and is only compatible with the `resource_fd`
2101-
resource type.
2099+
`import_external_memory`.
2100+
2101+
Note that some handle types can only be used in
2102+
combination with certain resource types, for example the `opaque_fd`
2103+
and `dma_buf` handle types are only used on Linux systems
2104+
and are only compatible with the `resource_fd` resource type.
2105+
The handle types supported by the device can be queried using the
2106+
`supports_importing_handle_type` device query.
21022107
21032108
```cpp
21042109
namespace sycl::ext::oneapi::experimental {
@@ -2139,6 +2144,10 @@ void *map_external_linear_memory(
21392144
uint64_t size, uint64_t offset,
21402145
const sycl::queue &syclQueue);
21412146
}
2147+
2148+
bool supports_importing_handle_type(
2149+
external_mem_handle_type externMemHandleType,
2150+
const sycl::device &syclDevice);
21422151
```
21432152
21442153
The resulting `external_mem` can then be mapped, where the resulting type
@@ -2728,3 +2737,5 @@ This query should be added in a later revision of the proposal.
27282737
|6.10|2025-05-09| - Add `unmap_external_image_memory` and
27292738
`unmap_external_linear_memory` APIs.
27302739
- Clarify how and when external memory should be unmapped.
2740+
|6.11|2025-06-16| - Add `external_mem_handle_type::dma_buf`
2741+
- Add `supports_importing_handle_type`

sycl/include/sycl/ext/oneapi/bindless_images.hpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -333,6 +333,16 @@ inline void unmap_external_image_memory(image_mem_handle mappedImageMem,
333333
syclQueue.get_context());
334334
}
335335

336+
/**
337+
* @brief Check if the device supports importing a handle of a specific type
338+
* @param externMemHandleType Type of external memory handle
339+
* @param syclDevice The device where we want to import memory
340+
* @return true if the device supports importing the specified handle type
341+
*/
342+
__SYCL_EXPORT bool
343+
supports_importing_handle_type(external_mem_handle_type externMemHandleType,
344+
const sycl::device &syclDevice);
345+
336346
/**
337347
* @brief Create an image and return the device image handle
338348
*

sycl/include/sycl/ext/oneapi/bindless_images_interop.hpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ enum class external_mem_handle_type {
2121
opaque_fd = 0,
2222
win32_nt_handle = 1,
2323
win32_nt_dx12_resource = 2,
24+
dma_buf = 3,
2425
};
2526

2627
// Types of external semaphore handles

sycl/source/detail/bindless_images.cpp

Lines changed: 53 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -389,6 +389,34 @@ create_image(void *devPtr, size_t pitch, const bindless_image_sampler &sampler,
389389
syclQueue.get_context());
390390
}
391391

392+
namespace detail {
393+
394+
/**
395+
* Converts SYCL external_mem_handle_type to the corresponding UR type.
396+
*
397+
* Note that this function does a simple conversion
398+
* and doesn't check the result validity for any specific scenario.
399+
*/
400+
constexpr ur_exp_external_mem_type_t
401+
to_ur_type(external_mem_handle_type externalMemHandleType) {
402+
switch (externalMemHandleType) {
403+
case external_mem_handle_type::opaque_fd:
404+
return UR_EXP_EXTERNAL_MEM_TYPE_OPAQUE_FD;
405+
case external_mem_handle_type::dma_buf:
406+
return UR_EXP_EXTERNAL_MEM_TYPE_DMA_BUF;
407+
case external_mem_handle_type::win32_nt_handle:
408+
return UR_EXP_EXTERNAL_MEM_TYPE_WIN32_NT;
409+
case external_mem_handle_type::win32_nt_dx12_resource:
410+
return UR_EXP_EXTERNAL_MEM_TYPE_WIN32_NT_DX12_RESOURCE;
411+
default:
412+
// This ensures that all cases have to be handled
413+
assert(false && "Invalid memory handle type");
414+
return UR_EXP_EXTERNAL_MEM_TYPE_OPAQUE_FD; // Fallback
415+
}
416+
}
417+
418+
} // namespace detail
419+
392420
template <>
393421
__SYCL_EXPORT external_mem import_external_memory<resource_fd>(
394422
external_mem_descriptor<resource_fd> externalMemDesc,
@@ -403,15 +431,18 @@ __SYCL_EXPORT external_mem import_external_memory<resource_fd>(
403431
urExternalMemDescriptor.stype = UR_STRUCTURE_TYPE_EXP_EXTERNAL_MEM_DESC;
404432
urExternalMemDescriptor.pNext = &urFileDescriptor;
405433

406-
// For `resource_fd` external memory type, the handle type is always
407-
// `OPAQUE_FD`. No need for a switch statement like we have for win32
408-
// resources.
434+
const auto urHandleType = detail::to_ur_type(externalMemDesc.handle_type);
435+
if ((urHandleType != UR_EXP_EXTERNAL_MEM_TYPE_OPAQUE_FD) &&
436+
(urHandleType != UR_EXP_EXTERNAL_MEM_TYPE_DMA_BUF)) {
437+
throw sycl::exception(sycl::make_error_code(sycl::errc::invalid),
438+
"Invalid memory handle type");
439+
}
440+
409441
Adapter
410442
->call<sycl::errc::invalid,
411443
sycl::detail::UrApiKind::urBindlessImagesImportExternalMemoryExp>(
412-
urCtx, urDevice, externalMemDesc.size_in_bytes,
413-
UR_EXP_EXTERNAL_MEM_TYPE_OPAQUE_FD, &urExternalMemDescriptor,
414-
&urExternalMem);
444+
urCtx, urDevice, externalMemDesc.size_in_bytes, urHandleType,
445+
&urExternalMemDescriptor, &urExternalMem);
415446

416447
return external_mem{urExternalMem};
417448
}
@@ -438,16 +469,9 @@ __SYCL_EXPORT external_mem import_external_memory<resource_win32_handle>(
438469
urExternalMemDescriptor.stype = UR_STRUCTURE_TYPE_EXP_EXTERNAL_MEM_DESC;
439470
urExternalMemDescriptor.pNext = &urWin32Handle;
440471

441-
// Select appropriate memory handle type.
442-
ur_exp_external_mem_type_t urHandleType;
443-
switch (externalMemDesc.handle_type) {
444-
case external_mem_handle_type::win32_nt_handle:
445-
urHandleType = UR_EXP_EXTERNAL_MEM_TYPE_WIN32_NT;
446-
break;
447-
case external_mem_handle_type::win32_nt_dx12_resource:
448-
urHandleType = UR_EXP_EXTERNAL_MEM_TYPE_WIN32_NT_DX12_RESOURCE;
449-
break;
450-
default:
472+
const auto urHandleType = detail::to_ur_type(externalMemDesc.handle_type);
473+
if ((urHandleType != UR_EXP_EXTERNAL_MEM_TYPE_WIN32_NT) &&
474+
(urHandleType != UR_EXP_EXTERNAL_MEM_TYPE_WIN32_NT_DX12_RESOURCE)) {
451475
throw sycl::exception(sycl::make_error_code(sycl::errc::invalid),
452476
"Invalid memory handle type");
453477
}
@@ -560,6 +584,19 @@ __SYCL_EXPORT void unmap_external_image_memory(
560584
free_image_mem(mappedImageMem, imageType, syclDevice, syclContext);
561585
}
562586

587+
__SYCL_EXPORT bool
588+
supports_importing_handle_type(external_mem_handle_type externMemHandleType,
589+
const sycl::device &syclDevice) {
590+
auto [urDevice, Adapter] = get_ur_handles(syclDevice);
591+
const auto urHandleType = detail::to_ur_type(externMemHandleType);
592+
ur_bool_t supportsExternalHandleType{0};
593+
Adapter->call<
594+
sycl::errc::invalid,
595+
sycl::detail::UrApiKind::urBindlessImagesSupportsImportingHandleTypeExp>(
596+
urDevice, urHandleType, &supportsExternalHandleType);
597+
return static_cast<bool>(supportsExternalHandleType);
598+
}
599+
563600
template <>
564601
__SYCL_EXPORT external_semaphore import_external_semaphore(
565602
external_semaphore_descriptor<resource_fd> externalSemaphoreDesc,

sycl/source/detail/context_impl.hpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -379,5 +379,11 @@ inline auto get_ur_handles(const sycl::device &syclDevice,
379379
sycl::detail::getSyclObjImpl(syclDevice)->getHandleRef();
380380
return std::tuple{urDevice, urCtx, Adapter};
381381
}
382+
inline auto get_ur_handles(const sycl::device &syclDevice) {
383+
auto &implDevice = *sycl::detail::getSyclObjImpl(syclDevice);
384+
ur_device_handle_t urDevice = implDevice.getHandleRef();
385+
return std::tuple{urDevice, implDevice.getAdapter()};
386+
}
387+
382388
} // namespace _V1
383389
} // namespace sycl

sycl/test-e2e/bindless_images/examples/example_6_import_memory_and_semaphores.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ int main() {
5050
sycl::ext::oneapi::experimental::resource_fd>
5151
output_ext_mem_desc{
5252
external_output_image_file_descriptor,
53-
sycl::ext::oneapi::experimental::external_mem_handle_type::opaque_fd,
53+
sycl::ext::oneapi::experimental::external_mem_handle_type::dma_buf,
5454
img_size_in_bytes};
5555

5656
// An external API semaphore will signal this semaphore before our SYCL

sycl/test-e2e/bindless_images/vulkan_interop/buffer_usm.cpp

Lines changed: 72 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -23,31 +23,22 @@
2323

2424
namespace syclexp = sycl::ext::oneapi::experimental;
2525

26-
template <typename InteropMemHandleT>
27-
void runSycl(const sycl::device &syclDevice, sycl::range<1> globalSize,
26+
template <syclexp::external_mem_handle_type ExtMemHandleTypeV,
27+
typename InteropMemHandleT>
28+
void runSycl(sycl::queue &syclQueue, sycl::range<1> globalSize,
2829
sycl::range<1> localSize, InteropMemHandleT extMemInHandle,
2930
InteropMemHandleT extMemOutHandle) {
30-
31-
sycl::queue syclQueue{syclDevice};
32-
3331
const size_t bufferSizeBytes = globalSize.size() * sizeof(uint32_t);
3432

35-
#ifdef _WIN32
36-
syclexp::external_mem_descriptor<syclexp::resource_win32_handle> extMemInDesc{
37-
extMemInHandle, syclexp::external_mem_handle_type::win32_nt_handle,
38-
bufferSizeBytes};
39-
syclexp::external_mem_descriptor<syclexp::resource_win32_handle>
40-
extMemOutDesc{extMemOutHandle,
41-
syclexp::external_mem_handle_type::win32_nt_handle,
42-
bufferSizeBytes};
43-
#else
44-
syclexp::external_mem_descriptor<syclexp::resource_fd> extMemInDesc{
45-
extMemInHandle, syclexp::external_mem_handle_type::opaque_fd,
46-
bufferSizeBytes};
47-
syclexp::external_mem_descriptor<syclexp::resource_fd> extMemOutDesc{
48-
extMemOutHandle, syclexp::external_mem_handle_type::opaque_fd,
49-
bufferSizeBytes};
50-
#endif
33+
using ResourceT =
34+
std::conditional_t<(ExtMemHandleTypeV ==
35+
syclexp::external_mem_handle_type::win32_nt_handle),
36+
syclexp::resource_win32_handle, syclexp::resource_fd>;
37+
38+
syclexp::external_mem_descriptor<ResourceT> extMemInDesc{
39+
extMemInHandle, ExtMemHandleTypeV, bufferSizeBytes};
40+
syclexp::external_mem_descriptor<ResourceT> extMemOutDesc{
41+
extMemOutHandle, ExtMemHandleTypeV, bufferSizeBytes};
5142

5243
// Extension: create interop memory handles.
5344
syclexp::external_mem externalMemIn =
@@ -64,13 +55,13 @@ void runSycl(const sycl::device &syclDevice, sycl::range<1> globalSize,
6455

6556
try {
6657
syclQueue.submit([&](sycl::handler &cgh) {
67-
cgh.parallel_for<class TestVkBufferUSMInterop>(
68-
sycl::nd_range<1>{globalSize, localSize}, [=](sycl::nd_item<1> it) {
69-
size_t index = it.get_global_id(0);
58+
cgh.parallel_for(sycl::nd_range<1>{globalSize, localSize},
59+
[=](sycl::nd_item<1> it) {
60+
size_t index = it.get_global_id(0);
7061

71-
uint32_t bufferValue = memIn[index];
72-
memOut[index] = bufferValue * 2;
73-
});
62+
uint32_t bufferValue = memIn[index];
63+
memOut[index] = bufferValue * 2;
64+
});
7465
});
7566

7667
// Wait for kernel completion before destroying external objects.
@@ -91,8 +82,25 @@ void runSycl(const sycl::device &syclDevice, sycl::range<1> globalSize,
9182
}
9283
}
9384

85+
template <syclexp::external_mem_handle_type ExtMemHandleTypeV>
9486
bool runTest(const sycl::device &syclDevice, sycl::range<1> bufferSize,
9587
sycl::range<1> localSize) {
88+
sycl::queue syclQueue{syclDevice};
89+
if constexpr (ExtMemHandleTypeV ==
90+
syclexp::external_mem_handle_type::dma_buf) {
91+
if (!supportsDmaBuf) {
92+
std::cout
93+
<< "dma_buf test skipped because Vulkan driver does not support it\n";
94+
return true;
95+
}
96+
if (!syclexp::supports_importing_handle_type(ExtMemHandleTypeV,
97+
syclDevice)) {
98+
std::cout
99+
<< "dma_buf test skipped because SYCL backend does not support it\n";
100+
return true;
101+
}
102+
}
103+
96104
const size_t bufferSizeElems = bufferSize[0];
97105
const size_t bufferSizeBytes = bufferSizeElems * sizeof(uint32_t);
98106

@@ -194,17 +202,26 @@ bool runTest(const sycl::device &syclDevice, sycl::range<1> bufferSize,
194202

195203
printString("Getting memory interop handles\n");
196204
// Get memory interop handles.
205+
const auto get_memory_handle = [](VkDeviceMemory vulkanDeviceMem) {
197206
#ifdef _WIN32
198-
auto bufferMemIn = vkutil::getMemoryWin32Handle(vkInputBufferMemory);
199-
auto bufferMemOut = vkutil::getMemoryWin32Handle(vkOutputBufferMemory);
207+
return vkutil::getMemoryWin32Handle(vulkanDeviceMem);
200208
#else
201-
auto bufferMemIn = vkutil::getMemoryOpaqueFD(vkInputBufferMemory);
202-
auto bufferMemOut = vkutil::getMemoryOpaqueFD(vkOutputBufferMemory);
209+
if constexpr (ExtMemHandleTypeV ==
210+
syclexp::external_mem_handle_type::dma_buf) {
211+
return vkutil::getMemoryOpaqueFD<
212+
VK_EXTERNAL_MEMORY_HANDLE_TYPE_DMA_BUF_BIT_EXT>(vulkanDeviceMem);
213+
} else {
214+
return vkutil::getMemoryOpaqueFD(vulkanDeviceMem);
215+
}
203216
#endif
217+
};
218+
auto bufferMemIn = get_memory_handle(vkInputBufferMemory);
219+
auto bufferMemOut = get_memory_handle(vkOutputBufferMemory);
204220

205221
// Call into SYCL to read from input buffer, and populate the output buffer.
206222
printString("Calling into SYCL with interop memory handles\n");
207-
runSycl(syclDevice, bufferSize, localSize, bufferMemIn, bufferMemOut);
223+
runSycl<ExtMemHandleTypeV>(syclQueue, bufferSize, localSize, bufferMemIn,
224+
bufferMemOut);
208225

209226
// Copy device buffer memory to temporary staging buffer, and back to host.
210227
printString("Copying buffer memory to host\n");
@@ -309,14 +326,35 @@ int main() {
309326
return EXIT_FAILURE;
310327
}
311328

312-
auto testPassed = runTest(syclDevice, {1024}, {256});
329+
const auto globalSize = sycl::range<1>{1024};
330+
const auto localSize = sycl::range<1>{256};
331+
#ifdef _WIN32
332+
const bool opaqueTestPassed =
333+
runTest<syclexp::external_mem_handle_type::win32_nt_handle>(
334+
syclDevice, globalSize, localSize);
335+
constexpr bool dmaBufTestPassed = true;
336+
// No check for opaqueTestPassed here, there is a common check later
337+
#else
338+
const bool opaqueTestPassed =
339+
runTest<syclexp::external_mem_handle_type::opaque_fd>(
340+
syclDevice, globalSize, localSize);
341+
if (!opaqueTestPassed) {
342+
std::cout << "opaque_fd test failed!\n";
343+
}
344+
const bool dmaBufTestPassed =
345+
runTest<syclexp::external_mem_handle_type::dma_buf>(
346+
syclDevice, globalSize, localSize);
347+
if (!dmaBufTestPassed) {
348+
std::cout << "dma_buf test failed!\n";
349+
}
350+
#endif
313351

314352
if (vkutil::cleanup() != VK_SUCCESS) {
315353
std::cerr << "Cleanup failed!\n";
316354
return EXIT_FAILURE;
317355
}
318356

319-
if (testPassed) {
357+
if (opaqueTestPassed && dmaBufTestPassed) {
320358
std::cout << "Test passed!\n";
321359
return EXIT_SUCCESS;
322360
}

0 commit comments

Comments
 (0)