From 7412973dc9c45b60a7233cbf9714babc53db559a Mon Sep 17 00:00:00 2001 From: Jose Lopez Date: Tue, 16 Sep 2025 18:41:49 +0100 Subject: [PATCH] Add tests for cl_khr_external_semaphore_dx_fence (#2482) - Add tests to import DirectX 12 fences as semaphores. - Add tests to export semaphores as DirectX 12 fences. - Add queries tests. - Add negative tests. Fixes: https://github.com/KhronosGroup/OpenCL-CTS/issues/2480 --- CMakeLists.txt | 1 + test_conformance/CMakeLists.txt | 3 + .../common/directx_wrapper/CMakeLists.txt | 13 + .../directx_wrapper/directx_wrapper.cpp | 71 ++++ .../directx_wrapper/directx_wrapper.hpp | 47 +++ test_conformance/extensions/CMakeLists.txt | 3 + .../CMakeLists.txt | 26 ++ .../main.cpp | 22 ++ .../semaphore_dx_fence_base.h | 117 +++++++ .../test_external_semaphore_dx_fence.cpp | 324 ++++++++++++++++++ ...est_external_semaphore_dx_fence_export.cpp | 220 ++++++++++++ ...emaphore_dx_fence_negative_wait_signal.cpp | 89 +++++ ...st_external_semaphore_dx_fence_queries.cpp | 69 ++++ 13 files changed, 1005 insertions(+) create mode 100644 test_conformance/common/directx_wrapper/CMakeLists.txt create mode 100644 test_conformance/common/directx_wrapper/directx_wrapper.cpp create mode 100644 test_conformance/common/directx_wrapper/directx_wrapper.hpp create mode 100644 test_conformance/extensions/cl_khr_external_semaphore_dx_fence/CMakeLists.txt create mode 100644 test_conformance/extensions/cl_khr_external_semaphore_dx_fence/main.cpp create mode 100644 test_conformance/extensions/cl_khr_external_semaphore_dx_fence/semaphore_dx_fence_base.h create mode 100644 test_conformance/extensions/cl_khr_external_semaphore_dx_fence/test_external_semaphore_dx_fence.cpp create mode 100644 test_conformance/extensions/cl_khr_external_semaphore_dx_fence/test_external_semaphore_dx_fence_export.cpp create mode 100644 test_conformance/extensions/cl_khr_external_semaphore_dx_fence/test_external_semaphore_dx_fence_negative_wait_signal.cpp create mode 100644 test_conformance/extensions/cl_khr_external_semaphore_dx_fence/test_external_semaphore_dx_fence_queries.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 90c343fc..5c0481ee 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -30,6 +30,7 @@ endif(USE_CL_EXPERIMENTAL) #----------------------------------------------------------- option(D3D10_IS_SUPPORTED "Run DirectX 10 interop tests" OFF) option(D3D11_IS_SUPPORTED "Run DirectX 11 interop tests" OFF) +option(D3D12_IS_SUPPORTED "Run DirectX 12 interop tests" OFF) option(GL_IS_SUPPORTED "Run OpenGL interop tests" OFF) option(GLES_IS_SUPPORTED "Run OpenGL ES interop tests" OFF) option(VULKAN_IS_SUPPORTED "Run Vulkan interop tests" OFF) diff --git a/test_conformance/CMakeLists.txt b/test_conformance/CMakeLists.txt index 1f1970af..d0e44961 100644 --- a/test_conformance/CMakeLists.txt +++ b/test_conformance/CMakeLists.txt @@ -56,6 +56,9 @@ if(VULKAN_IS_SUPPORTED) add_subdirectory( common/vulkan_wrapper ) add_subdirectory( vulkan ) endif() +if(D3D12_IS_SUPPORTED) + add_subdirectory(common/directx_wrapper) +endif () file(GLOB CSV_FILES "opencl_conformance_tests_*.csv") diff --git a/test_conformance/common/directx_wrapper/CMakeLists.txt b/test_conformance/common/directx_wrapper/CMakeLists.txt new file mode 100644 index 00000000..58ae19a8 --- /dev/null +++ b/test_conformance/common/directx_wrapper/CMakeLists.txt @@ -0,0 +1,13 @@ +set(DIRECTX_WRAPPER_SOURCES + directx_wrapper.cpp +) + +add_library(directx_wrapper STATIC ${DIRECTX_WRAPPER_SOURCES}) + +include_directories(${CMAKE_CURRENT_SOURCE_DIR}) + +include_directories(${CLConform_INCLUDE_DIR}) + +if (WIN32) + target_link_libraries(directx_wrapper d3d12) +endif () diff --git a/test_conformance/common/directx_wrapper/directx_wrapper.cpp b/test_conformance/common/directx_wrapper/directx_wrapper.cpp new file mode 100644 index 00000000..2255a541 --- /dev/null +++ b/test_conformance/common/directx_wrapper/directx_wrapper.cpp @@ -0,0 +1,71 @@ +// +// Copyright (c) 2025 The Khronos Group Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#include "directx_wrapper.hpp" + +DirectXWrapper::DirectXWrapper() +{ + + HRESULT hr = D3D12CreateDevice(nullptr, D3D_FEATURE_LEVEL_12_0, + IID_PPV_ARGS(&dx_device)); + if (FAILED(hr)) + { + throw std::runtime_error("Failed to create DirectX 12 device"); + } + + D3D12_COMMAND_QUEUE_DESC desc{}; + desc.Flags = D3D12_COMMAND_QUEUE_FLAG_NONE; + desc.Type = D3D12_COMMAND_LIST_TYPE_DIRECT; + hr = dx_device->CreateCommandQueue(&desc, IID_PPV_ARGS(&dx_command_queue)); + if (FAILED(hr)) + { + throw std::runtime_error("Failed to create DirectX 12 command queue"); + } + + hr = dx_device->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, + IID_PPV_ARGS(&dx_command_allocator)); + if (FAILED(hr)) + { + throw std::runtime_error( + "Failed to create DirectX 12 command allocator"); + } +} + +ID3D12Device* DirectXWrapper::getDXDevice() const { return dx_device.Get(); } + +ID3D12CommandQueue* DirectXWrapper::getDXCommandQueue() const +{ + return dx_command_queue.Get(); +} +ID3D12CommandAllocator* DirectXWrapper::getDXCommandAllocator() const +{ + return dx_command_allocator.Get(); +} + +DirectXFenceWrapper::DirectXFenceWrapper(ID3D12Device* dx_device) + : dx_device(dx_device) +{ + if (!dx_device) + { + throw std::runtime_error("ID3D12Device is not valid"); + } + const HRESULT hr = dx_device->CreateFence(0, D3D12_FENCE_FLAG_SHARED, + IID_PPV_ARGS(&dx_fence)); + if (FAILED(hr)) + { + throw std::runtime_error("Failed to create the DirectX fence"); + } +} diff --git a/test_conformance/common/directx_wrapper/directx_wrapper.hpp b/test_conformance/common/directx_wrapper/directx_wrapper.hpp new file mode 100644 index 00000000..dec85b78 --- /dev/null +++ b/test_conformance/common/directx_wrapper/directx_wrapper.hpp @@ -0,0 +1,47 @@ +// +// Copyright (c) 2025 The Khronos Group Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#pragma once + +#include +#include +#include + +using namespace Microsoft::WRL; + +class DirectXWrapper { +public: + DirectXWrapper(); + + ID3D12Device* getDXDevice() const; + ID3D12CommandQueue* getDXCommandQueue() const; + ID3D12CommandAllocator* getDXCommandAllocator() const; + +protected: + ComPtr dx_device = nullptr; + ComPtr dx_command_queue = nullptr; + ComPtr dx_command_allocator = nullptr; +}; + +class DirectXFenceWrapper { +public: + DirectXFenceWrapper(ID3D12Device* dx_device); + ID3D12Fence* operator*() const { return dx_fence.Get(); } + +private: + ComPtr dx_fence = nullptr; + ComPtr dx_device = nullptr; +}; \ No newline at end of file diff --git a/test_conformance/extensions/CMakeLists.txt b/test_conformance/extensions/CMakeLists.txt index 2fee828a..a2af536e 100644 --- a/test_conformance/extensions/CMakeLists.txt +++ b/test_conformance/extensions/CMakeLists.txt @@ -15,3 +15,6 @@ add_subdirectory( cl_ext_buffer_device_address ) if(VULKAN_IS_SUPPORTED) add_subdirectory( cl_khr_external_semaphore ) endif() +if(D3D12_IS_SUPPORTED) + add_subdirectory( cl_khr_external_semaphore_dx_fence ) +endif() \ No newline at end of file diff --git a/test_conformance/extensions/cl_khr_external_semaphore_dx_fence/CMakeLists.txt b/test_conformance/extensions/cl_khr_external_semaphore_dx_fence/CMakeLists.txt new file mode 100644 index 00000000..08b03b42 --- /dev/null +++ b/test_conformance/extensions/cl_khr_external_semaphore_dx_fence/CMakeLists.txt @@ -0,0 +1,26 @@ +if (WIN32) + include_directories(${CLConform_SOURCE_DIR}/test_common/harness + ${CLConform_INCLUDE_DIR}) + link_directories(${CL_LIB_DIR}) + + list(APPEND CLConform_LIBRARIES directx_wrapper) + + set(MODULE_NAME CL_KHR_EXTERNAL_SEMAPHORE_DX_FENCE) + + set(${MODULE_NAME}_SOURCES + main.cpp + test_external_semaphore_dx_fence.cpp + test_external_semaphore_dx_fence_negative_wait_signal.cpp + test_external_semaphore_dx_fence_queries.cpp + test_external_semaphore_dx_fence_export.cpp + ) + + set_source_files_properties( + ${MODULE_NAME}_SOURCES + PROPERTIES LANGUAGE CXX) + + include_directories(${CMAKE_CURRENT_SOURCE_DIR}) + include_directories("../../common/directx_wrapper") + include(../../CMakeCommon.txt) +endif (WIN32) + diff --git a/test_conformance/extensions/cl_khr_external_semaphore_dx_fence/main.cpp b/test_conformance/extensions/cl_khr_external_semaphore_dx_fence/main.cpp new file mode 100644 index 00000000..85c8fc7f --- /dev/null +++ b/test_conformance/extensions/cl_khr_external_semaphore_dx_fence/main.cpp @@ -0,0 +1,22 @@ +// +// Copyright (c) 2025 The Khronos Group Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#include "harness/testHarness.h" + +int main(int argc, const char *argv[]) +{ + return runTestHarness(argc, argv, test_registry::getInstance().num_tests(), + test_registry::getInstance().definitions(), false, 0); +} \ No newline at end of file diff --git a/test_conformance/extensions/cl_khr_external_semaphore_dx_fence/semaphore_dx_fence_base.h b/test_conformance/extensions/cl_khr_external_semaphore_dx_fence/semaphore_dx_fence_base.h new file mode 100644 index 00000000..f8ccb570 --- /dev/null +++ b/test_conformance/extensions/cl_khr_external_semaphore_dx_fence/semaphore_dx_fence_base.h @@ -0,0 +1,117 @@ +// +// Copyright (c) 2025 The Khronos Group Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#pragma once + +#include "harness/typeWrappers.h" +#include "harness/extensionHelpers.h" +#include "harness/errorHelpers.h" +#include "directx_wrapper.hpp" + +class CLDXSemaphoreWrapper { +public: + CLDXSemaphoreWrapper(cl_device_id device, cl_context context, + ID3D12Device* dx_device) + : device(device), context(context), dx_device(dx_device){}; + + int createSemaphoreFromFence(ID3D12Fence* fence) + { + cl_int errcode = CL_SUCCESS; + + GET_PFN(device, clCreateSemaphoreWithPropertiesKHR); + + const HRESULT hr = dx_device->CreateSharedHandle( + fence, nullptr, GENERIC_ALL, nullptr, &fence_handle); + test_error(FAILED(hr), "Failed to get shared handle from D3D12 fence"); + + cl_semaphore_properties_khr sem_props[] = { + static_cast(CL_SEMAPHORE_TYPE_KHR), + static_cast( + CL_SEMAPHORE_TYPE_BINARY_KHR), + static_cast( + CL_SEMAPHORE_HANDLE_D3D12_FENCE_KHR), + reinterpret_cast(fence_handle), 0 + }; + semaphore = + clCreateSemaphoreWithPropertiesKHR(context, sem_props, &errcode); + test_error(errcode, "Could not create semaphore"); + + return CL_SUCCESS; + } + + ~CLDXSemaphoreWrapper() + { + releaseSemaphore(); + if (fence_handle) + { + CloseHandle(fence_handle); + } + }; + + const cl_semaphore_khr* operator&() const { return &semaphore; }; + cl_semaphore_khr operator*() const { return semaphore; }; + + HANDLE getHandle() const { return fence_handle; }; + +private: + cl_semaphore_khr semaphore; + ComPtr fence; + HANDLE fence_handle; + cl_device_id device; + cl_context context; + ComPtr dx_device; + + int releaseSemaphore() const + { + GET_PFN(device, clReleaseSemaphoreKHR); + + if (semaphore) + { + clReleaseSemaphoreKHR(semaphore); + } + + return CL_SUCCESS; + } +}; + +static bool +is_import_handle_available(cl_device_id device, + const cl_external_memory_handle_type_khr handle_type) +{ + int errcode = CL_SUCCESS; + size_t import_types_size = 0; + errcode = + clGetDeviceInfo(device, CL_DEVICE_SEMAPHORE_IMPORT_HANDLE_TYPES_KHR, 0, + nullptr, &import_types_size); + if (errcode != CL_SUCCESS) + { + log_error("Could not query import semaphore handle types"); + return false; + } + std::vector import_types( + import_types_size / sizeof(cl_external_semaphore_handle_type_khr)); + errcode = + clGetDeviceInfo(device, CL_DEVICE_SEMAPHORE_IMPORT_HANDLE_TYPES_KHR, + import_types_size, import_types.data(), nullptr); + if (errcode != CL_SUCCESS) + { + log_error("Could not query import semaphore handle types"); + return false; + } + + return std::find(import_types.begin(), import_types.end(), handle_type) + != import_types.end(); +} \ No newline at end of file diff --git a/test_conformance/extensions/cl_khr_external_semaphore_dx_fence/test_external_semaphore_dx_fence.cpp b/test_conformance/extensions/cl_khr_external_semaphore_dx_fence/test_external_semaphore_dx_fence.cpp new file mode 100644 index 00000000..569fb204 --- /dev/null +++ b/test_conformance/extensions/cl_khr_external_semaphore_dx_fence/test_external_semaphore_dx_fence.cpp @@ -0,0 +1,324 @@ +// +// Copyright (c) 2025 The Khronos Group Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#include "semaphore_dx_fence_base.h" + +// Confirm that a signal followed by a wait in OpenCL will complete successfully +REGISTER_TEST(test_external_semaphores_signal_wait) +{ + int errcode = CL_SUCCESS; + const DirectXWrapper dx_wrapper; + + REQUIRE_EXTENSION("cl_khr_external_semaphore"); + REQUIRE_EXTENSION("cl_khr_external_semaphore_dx_fence"); + + // Obtain pointers to semaphore's API + GET_PFN(device, clCreateSemaphoreWithPropertiesKHR); + GET_PFN(device, clReleaseSemaphoreKHR); + GET_PFN(device, clEnqueueSignalSemaphoresKHR); + GET_PFN(device, clEnqueueWaitSemaphoresKHR); + + test_error(!is_import_handle_available(device, + CL_SEMAPHORE_HANDLE_D3D12_FENCE_KHR), + "Could not find CL_SEMAPHORE_HANDLE_D3D12_FENCE_KHR between the " + "supported import types"); + + // Import D3D12 fence into OpenCL + const DirectXFenceWrapper fence(dx_wrapper.getDXDevice()); + CLDXSemaphoreWrapper semaphore(device, context, dx_wrapper.getDXDevice()); + test_error(semaphore.createSemaphoreFromFence(*fence), + "Could not create semaphore"); + + log_info("Calling clEnqueueSignalSemaphoresKHR\n"); + constexpr cl_semaphore_payload_khr semaphore_payload = 1; + clEventWrapper signal_event; + errcode = clEnqueueSignalSemaphoresKHR( + queue, 1, &semaphore, &semaphore_payload, 0, nullptr, &signal_event); + test_error(errcode, "Failed to signal semaphore"); + + log_info("Calling clEnqueueWaitSemaphoresKHR\n"); + clEventWrapper wait_event; + errcode = clEnqueueWaitSemaphoresKHR( + queue, 1, &semaphore, &semaphore_payload, 0, nullptr, &wait_event); + test_error(errcode, "Failed to wait semaphore"); + + errcode = clFinish(queue); + test_error(errcode, "Could not finish queue"); + + // Verify that the events completed. + test_assert_event_complete(signal_event); + test_assert_event_complete(wait_event); + + return TEST_PASS; +} + +// Confirm that a wait in OpenCL followed by a CPU signal in DX12 will complete +// successfully +REGISTER_TEST(test_external_semaphores_signal_dx_cpu) +{ + int errcode = CL_SUCCESS; + const DirectXWrapper dx_wrapper; + + REQUIRE_EXTENSION("cl_khr_external_semaphore"); + REQUIRE_EXTENSION("cl_khr_external_semaphore_dx_fence"); + + // Obtain pointers to semaphore's API + GET_PFN(device, clCreateSemaphoreWithPropertiesKHR); + GET_PFN(device, clReleaseSemaphoreKHR); + GET_PFN(device, clEnqueueSignalSemaphoresKHR); + GET_PFN(device, clEnqueueWaitSemaphoresKHR); + + test_error(!is_import_handle_available(device, + CL_SEMAPHORE_HANDLE_D3D12_FENCE_KHR), + "Could not find CL_SEMAPHORE_HANDLE_D3D12_FENCE_KHR between the " + "supported import types"); + + // Import D3D12 fence into OpenCL + const DirectXFenceWrapper fence(dx_wrapper.getDXDevice()); + CLDXSemaphoreWrapper semaphore(device, context, dx_wrapper.getDXDevice()); + test_error(semaphore.createSemaphoreFromFence(*fence), + "Could not create semaphore"); + + log_info("Calling clEnqueueWaitSemaphoresKHR\n"); + constexpr cl_semaphore_payload_khr semaphore_payload = 1; + clEventWrapper wait_event; + errcode = clEnqueueWaitSemaphoresKHR( + queue, 1, &semaphore, &semaphore_payload, 0, nullptr, &wait_event); + test_error(errcode, "Failed to call clEnqueueWaitSemaphoresKHR"); + + log_info("Calling d3d12_fence->Signal()\n"); + const HRESULT hr = (*fence)->Signal(semaphore_payload); + test_error(FAILED(hr), "Failed to signal D3D12 fence"); + + errcode = clFinish(queue); + test_error(errcode, "Could not finish queue"); + + test_assert_event_complete(wait_event); + + return TEST_PASS; +} + +// Confirm that a wait in OpenCL followed by a GPU signal in DX12 will complete +// successfully +REGISTER_TEST(test_external_semaphores_signal_dx_gpu) +{ + int errcode = CL_SUCCESS; + const DirectXWrapper dx_wrapper; + + REQUIRE_EXTENSION("cl_khr_external_semaphore"); + REQUIRE_EXTENSION("cl_khr_external_semaphore_dx_fence"); + + // Obtain pointers to semaphore's API + GET_PFN(device, clCreateSemaphoreWithPropertiesKHR); + GET_PFN(device, clReleaseSemaphoreKHR); + GET_PFN(device, clEnqueueSignalSemaphoresKHR); + GET_PFN(device, clEnqueueWaitSemaphoresKHR); + + test_error(!is_import_handle_available(device, + CL_SEMAPHORE_HANDLE_D3D12_FENCE_KHR), + "Could not find CL_SEMAPHORE_HANDLE_D3D12_FENCE_KHR between the " + "supported import types"); + + // Import D3D12 fence into OpenCL + const DirectXFenceWrapper fence(dx_wrapper.getDXDevice()); + CLDXSemaphoreWrapper semaphore(device, context, dx_wrapper.getDXDevice()); + test_error(semaphore.createSemaphoreFromFence(*fence), + "Could not create semaphore"); + + log_info("Calling clEnqueueWaitSemaphoresKHR\n"); + constexpr cl_semaphore_payload_khr semaphore_payload = 1; + clEventWrapper wait_event; + errcode = clEnqueueWaitSemaphoresKHR( + queue, 1, &semaphore, &semaphore_payload, 0, nullptr, &wait_event); + test_error(errcode, "Failed to call clEnqueueWaitSemaphoresKHR"); + + log_info("Calling d3d12_command_queue->Signal()\n"); + const HRESULT hr = + dx_wrapper.getDXCommandQueue()->Signal(*fence, semaphore_payload); + test_error(FAILED(hr), "Failed to signal D3D12 fence"); + + errcode = clFinish(queue); + test_error(errcode, "Could not finish queue"); + + test_assert_event_complete(wait_event); + + return TEST_PASS; +} + +// Confirm that interlocking waits between OpenCL and DX12 will complete +// successfully +REGISTER_TEST(test_external_semaphores_cl_dx_interlock) +{ + int errcode = CL_SUCCESS; + const DirectXWrapper dx_wrapper; + + REQUIRE_EXTENSION("cl_khr_external_semaphore"); + REQUIRE_EXTENSION("cl_khr_external_semaphore_dx_fence"); + + // Obtain pointers to semaphore's API + GET_PFN(device, clCreateSemaphoreWithPropertiesKHR); + GET_PFN(device, clReleaseSemaphoreKHR); + GET_PFN(device, clEnqueueSignalSemaphoresKHR); + GET_PFN(device, clEnqueueWaitSemaphoresKHR); + + test_error(!is_import_handle_available(device, + CL_SEMAPHORE_HANDLE_D3D12_FENCE_KHR), + "Could not find CL_SEMAPHORE_HANDLE_D3D12_FENCE_KHR between the " + "supported import types"); + + // Import D3D12 fence into OpenCL + const DirectXFenceWrapper fence(dx_wrapper.getDXDevice()); + CLDXSemaphoreWrapper semaphore(device, context, dx_wrapper.getDXDevice()); + test_error(semaphore.createSemaphoreFromFence(*fence), + "Could not create semaphore"); + + log_info("Calling d3d12_command_queue->Wait(1)\n"); + cl_semaphore_payload_khr semaphore_payload = 1; + HRESULT hr = + dx_wrapper.getDXCommandQueue()->Wait(*fence, semaphore_payload); + test_error(FAILED(hr), "Failed to wait on D3D12 fence"); + + log_info("Calling d3d12_command_queue->Signal(2)\n"); + hr = dx_wrapper.getDXCommandQueue()->Signal(*fence, semaphore_payload + 1); + test_error(FAILED(hr), "Failed to signal D3D12 fence"); + + log_info("Calling clEnqueueSignalSemaphoresKHR(1)\n"); + clEventWrapper signal_event; + errcode = clEnqueueSignalSemaphoresKHR( + queue, 1, &semaphore, &semaphore_payload, 0, nullptr, &signal_event); + test_error(errcode, "Failed to call clEnqueueSignalSemaphoresKHR"); + + log_info("Calling clEnqueueWaitSemaphoresKHR(2)\n"); + semaphore_payload += 1; + clEventWrapper wait_event; + errcode = clEnqueueWaitSemaphoresKHR( + queue, 1, &semaphore, &semaphore_payload, 0, nullptr, &wait_event); + test_error(errcode, "Failed to call clEnqueueWaitSemaphoresKHR"); + + errcode = clFinish(queue); + test_error(errcode, "Could not finish queue"); + + test_assert_event_complete(wait_event); + test_assert_event_complete(signal_event); + + return TEST_PASS; +} + +// Confirm that multiple waits in OpenCL followed by signals in DX12 and waits +// in DX12 followed by signals in OpenCL complete successfully +REGISTER_TEST(test_external_semaphores_multiple_wait_signal) +{ + int errcode = CL_SUCCESS; + const DirectXWrapper dx_wrapper; + + REQUIRE_EXTENSION("cl_khr_external_semaphore"); + REQUIRE_EXTENSION("cl_khr_external_semaphore_dx_fence"); + + // Obtain pointers to semaphore's API + GET_PFN(device, clCreateSemaphoreWithPropertiesKHR); + GET_PFN(device, clReleaseSemaphoreKHR); + GET_PFN(device, clEnqueueSignalSemaphoresKHR); + GET_PFN(device, clEnqueueWaitSemaphoresKHR); + + test_error(!is_import_handle_available(device, + CL_SEMAPHORE_HANDLE_D3D12_FENCE_KHR), + "Could not find CL_SEMAPHORE_HANDLE_D3D12_FENCE_KHR between the " + "supported import types"); + + // Import D3D12 fence into OpenCL + const DirectXFenceWrapper fence_1(dx_wrapper.getDXDevice()); + CLDXSemaphoreWrapper semaphore_1(device, context, dx_wrapper.getDXDevice()); + test_error(semaphore_1.createSemaphoreFromFence(*fence_1), + "Could not create semaphore"); + + const DirectXFenceWrapper fence_2(dx_wrapper.getDXDevice()); + CLDXSemaphoreWrapper semaphore_2(device, context, dx_wrapper.getDXDevice()); + test_error(semaphore_2.createSemaphoreFromFence(*fence_2), + "Could not create semaphore"); + + const cl_semaphore_khr semaphore_list[] = { *semaphore_1, *semaphore_2 }; + constexpr cl_semaphore_payload_khr semaphore_payload = 1; + cl_semaphore_payload_khr semaphore_payload_list[] = { + semaphore_payload, semaphore_payload + 1 + }; + + log_info("Calling clEnqueueWaitSemaphoresKHR\n"); + clEventWrapper wait_event; + errcode = clEnqueueWaitSemaphoresKHR(queue, 2, semaphore_list, + semaphore_payload_list, 0, nullptr, + &wait_event); + test_error(errcode, "Failed to call clEnqueueWaitSemaphoresKHR"); + + log_info("Calling d3d12_command_queue->Signal()\n"); + HRESULT hr = + dx_wrapper.getDXCommandQueue()->Signal(*fence_2, semaphore_payload + 1); + test_error(FAILED(hr), "Failed to signal D3D12 fence 2"); + hr = dx_wrapper.getDXCommandQueue()->Signal(*fence_1, semaphore_payload); + test_error(FAILED(hr), "Failed to signal D3D12 fence 1"); + + log_info("Calling d3d12_command_queue->Wait() with different payloads\n"); + hr = dx_wrapper.getDXCommandQueue()->Wait(*fence_1, semaphore_payload + 3); + test_error(FAILED(hr), "Failed to wait on D3D12 fence 1"); + hr = dx_wrapper.getDXCommandQueue()->Wait(*fence_2, semaphore_payload + 2); + test_error(FAILED(hr), "Failed to wait on D3D12 fence 2"); + + errcode = clFinish(queue); + test_error(errcode, "Could not finish queue"); + + test_assert_event_complete(wait_event); + + semaphore_payload_list[0] = semaphore_payload + 3; + semaphore_payload_list[1] = semaphore_payload + 2; + + log_info("Calling clEnqueueSignalSemaphoresKHR\n"); + clEventWrapper signal_event; + errcode = clEnqueueSignalSemaphoresKHR(queue, 2, semaphore_list, + semaphore_payload_list, 0, nullptr, + &signal_event); + test_error(errcode, "Could not call clEnqueueSignalSemaphoresKHR"); + + // Wait until the GPU has completed commands up to this fence point. + log_info("Waiting for D3D12 command queue completion\n"); + if ((*fence_1)->GetCompletedValue() < semaphore_payload_list[0]) + { + const HANDLE event_handle = + CreateEventEx(nullptr, false, false, EVENT_ALL_ACCESS); + hr = (*fence_1)->SetEventOnCompletion(semaphore_payload_list[0], + event_handle); + test_error(FAILED(hr), + "Failed to set D3D12 fence 1 event on completion"); + WaitForSingleObject(event_handle, INFINITE); + CloseHandle(event_handle); + } + if ((*fence_2)->GetCompletedValue() < semaphore_payload_list[1]) + { + const HANDLE event_handle = + CreateEventEx(nullptr, false, false, EVENT_ALL_ACCESS); + hr = (*fence_2)->SetEventOnCompletion(semaphore_payload_list[1], + event_handle); + test_error(FAILED(hr), + "Failed to set D3D12 fence 2 event on completion"); + WaitForSingleObject(event_handle, INFINITE); + CloseHandle(event_handle); + } + + errcode = clFinish(queue); + test_error(errcode, "Could not finish queue"); + + test_assert_event_complete(signal_event); + + return TEST_PASS; +} \ No newline at end of file diff --git a/test_conformance/extensions/cl_khr_external_semaphore_dx_fence/test_external_semaphore_dx_fence_export.cpp b/test_conformance/extensions/cl_khr_external_semaphore_dx_fence/test_external_semaphore_dx_fence_export.cpp new file mode 100644 index 00000000..c54cf61b --- /dev/null +++ b/test_conformance/extensions/cl_khr_external_semaphore_dx_fence/test_external_semaphore_dx_fence_export.cpp @@ -0,0 +1,220 @@ +// +// Copyright (c) 2025 The Khronos Group Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#include "semaphore_dx_fence_base.h" + +// Confirm that a wait followed by a signal in DirectX 12 using an exported +// semaphore will complete successfully +REGISTER_TEST(test_external_semaphores_export_dx_signal) +{ + int errcode = CL_SUCCESS; + const DirectXWrapper dx_wrapper; + + REQUIRE_EXTENSION("cl_khr_external_semaphore"); + REQUIRE_EXTENSION("cl_khr_external_semaphore_dx_fence"); + + // Obtain pointers to semaphore's API + GET_PFN(device, clCreateSemaphoreWithPropertiesKHR); + GET_PFN(device, clReleaseSemaphoreKHR); + GET_PFN(device, clEnqueueSignalSemaphoresKHR); + GET_PFN(device, clEnqueueWaitSemaphoresKHR); + GET_PFN(device, clGetSemaphoreInfoKHR); + GET_PFN(device, clGetSemaphoreHandleForTypeKHR); + + size_t export_types_size = 0; + errcode = + clGetDeviceInfo(device, CL_DEVICE_SEMAPHORE_EXPORT_HANDLE_TYPES_KHR, 0, + nullptr, &export_types_size); + test_error(errcode, "Could not query export semaphore handle types"); + std::vector export_types( + export_types_size / sizeof(cl_external_semaphore_handle_type_khr)); + errcode = + clGetDeviceInfo(device, CL_DEVICE_SEMAPHORE_EXPORT_HANDLE_TYPES_KHR, + export_types_size, export_types.data(), nullptr); + test_error(errcode, "Could not query export semaphore handle types"); + + if (std::find(export_types.begin(), export_types.end(), + CL_SEMAPHORE_HANDLE_D3D12_FENCE_KHR) + == export_types.end()) + { + log_info("Could not find CL_SEMAPHORE_HANDLE_D3D12_FENCE_KHR between " + "the supported export types\n"); + return TEST_FAIL; + } + + constexpr cl_semaphore_properties_khr sem_props[] = { + static_cast(CL_SEMAPHORE_TYPE_KHR), + static_cast(CL_SEMAPHORE_TYPE_BINARY_KHR), + static_cast( + CL_SEMAPHORE_EXPORT_HANDLE_TYPES_KHR), + static_cast( + CL_SEMAPHORE_HANDLE_D3D12_FENCE_KHR), + static_cast( + CL_SEMAPHORE_EXPORT_HANDLE_TYPES_LIST_END_KHR), + 0 + }; + cl_semaphore_khr semaphore = + clCreateSemaphoreWithPropertiesKHR(context, sem_props, &errcode); + test_error(errcode, "Could not create semaphore"); + + cl_bool is_exportable = CL_FALSE; + errcode = + clGetSemaphoreInfoKHR(semaphore, CL_SEMAPHORE_EXPORTABLE_KHR, + sizeof(is_exportable), &is_exportable, nullptr); + test_error(errcode, "Could not get semaphore info"); + test_error(!is_exportable, "Semaphore is not exportable"); + + log_info("Calling clEnqueueWaitSemaphoresKHR\n"); + constexpr cl_semaphore_payload_khr semaphore_payload = 1; + clEventWrapper wait_event; + errcode = clEnqueueWaitSemaphoresKHR( + queue, 1, &semaphore, &semaphore_payload, 0, nullptr, &wait_event); + test_error(errcode, "Failed to wait semaphore"); + + HANDLE semaphore_handle = nullptr; + errcode = clGetSemaphoreHandleForTypeKHR( + semaphore, device, CL_SEMAPHORE_HANDLE_D3D12_FENCE_KHR, + sizeof(semaphore_handle), &semaphore_handle, nullptr); + test_error(errcode, "Could not get semaphore handle"); + + ID3D12Fence *fence = nullptr; + errcode = dx_wrapper.getDXDevice()->OpenSharedHandle(semaphore_handle, + IID_PPV_ARGS(&fence)); + test_error(errcode, "Could not open semaphore handle"); + + log_info("Calling fence->Signal()\n"); + const HRESULT hr = fence->Signal(semaphore_payload); + test_error(FAILED(hr), "Failed to signal D3D12 fence"); + + errcode = clFinish(queue); + test_error(errcode, "Could not finish queue"); + + test_assert_event_complete(wait_event); + + // Release resources + CloseHandle(semaphore_handle); + test_error(clReleaseSemaphoreKHR(semaphore), "Could not release semaphore"); + fence->Release(); + + return TEST_PASS; +} + +// Confirm that a signal in OpenCL followed by a wait in DirectX 12 using an +// exported semaphore will complete successfully +REGISTER_TEST(test_external_semaphores_export_dx_wait) +{ + int errcode = CL_SUCCESS; + const DirectXWrapper dx_wrapper; + + REQUIRE_EXTENSION("cl_khr_external_semaphore"); + REQUIRE_EXTENSION("cl_khr_external_semaphore_dx_fence"); + + // Obtain pointers to semaphore's API + GET_PFN(device, clCreateSemaphoreWithPropertiesKHR); + GET_PFN(device, clReleaseSemaphoreKHR); + GET_PFN(device, clEnqueueSignalSemaphoresKHR); + GET_PFN(device, clEnqueueWaitSemaphoresKHR); + GET_PFN(device, clGetSemaphoreInfoKHR); + GET_PFN(device, clGetSemaphoreHandleForTypeKHR); + + size_t export_types_size = 0; + errcode = + clGetDeviceInfo(device, CL_DEVICE_SEMAPHORE_EXPORT_HANDLE_TYPES_KHR, 0, + nullptr, &export_types_size); + test_error(errcode, "Could not query export semaphore handle types"); + std::vector export_types( + export_types_size / sizeof(cl_external_semaphore_handle_type_khr)); + errcode = + clGetDeviceInfo(device, CL_DEVICE_SEMAPHORE_EXPORT_HANDLE_TYPES_KHR, + export_types_size, export_types.data(), nullptr); + test_error(errcode, "Could not query export semaphore handle types"); + + if (std::find(export_types.begin(), export_types.end(), + CL_SEMAPHORE_HANDLE_D3D12_FENCE_KHR) + == export_types.end()) + { + log_info("Could not find CL_SEMAPHORE_HANDLE_D3D12_FENCE_KHR between " + "the supported export types\n"); + return TEST_FAIL; + } + + constexpr cl_semaphore_properties_khr sem_props[] = { + static_cast(CL_SEMAPHORE_TYPE_KHR), + static_cast(CL_SEMAPHORE_TYPE_BINARY_KHR), + static_cast( + CL_SEMAPHORE_EXPORT_HANDLE_TYPES_KHR), + static_cast( + CL_SEMAPHORE_HANDLE_D3D12_FENCE_KHR), + static_cast( + CL_SEMAPHORE_EXPORT_HANDLE_TYPES_LIST_END_KHR), + 0 + }; + cl_semaphore_khr semaphore = + clCreateSemaphoreWithPropertiesKHR(context, sem_props, &errcode); + test_error(errcode, "Could not create semaphore"); + + cl_bool is_exportable = CL_FALSE; + errcode = + clGetSemaphoreInfoKHR(semaphore, CL_SEMAPHORE_EXPORTABLE_KHR, + sizeof(is_exportable), &is_exportable, nullptr); + test_error(errcode, "Could not get semaphore info"); + test_error(!is_exportable, "Semaphore is not exportable"); + + log_info("Calling clEnqueueSignalSemaphoresKHR\n"); + constexpr cl_semaphore_payload_khr semaphore_payload = 1; + clEventWrapper signal_event; + errcode = clEnqueueSignalSemaphoresKHR( + queue, 1, &semaphore, &semaphore_payload, 0, nullptr, &signal_event); + test_error(errcode, "Failed to signal semaphore"); + + HANDLE semaphore_handle = nullptr; + errcode = clGetSemaphoreHandleForTypeKHR( + semaphore, device, CL_SEMAPHORE_HANDLE_D3D12_FENCE_KHR, + sizeof(semaphore_handle), &semaphore_handle, nullptr); + test_error(errcode, "Could not get semaphore handle"); + + ID3D12Fence *fence = nullptr; + errcode = dx_wrapper.getDXDevice()->OpenSharedHandle(semaphore_handle, + IID_PPV_ARGS(&fence)); + test_error(errcode, "Could not open semaphore handle"); + + log_info("Calling dx_wrapper.get_d3d12_command_queue()->Wait()\n"); + HRESULT hr = dx_wrapper.getDXCommandQueue()->Wait(fence, semaphore_payload); + test_error(FAILED(hr), "Failed to wait on D3D12 fence"); + + log_info("Calling WaitForSingleObject\n"); + if (fence->GetCompletedValue() < semaphore_payload) + { + const HANDLE event = + CreateEventEx(nullptr, false, false, EVENT_ALL_ACCESS); + hr = fence->SetEventOnCompletion(semaphore_payload, event); + test_error(FAILED(hr), "Failed to set event on completion"); + WaitForSingleObject(event, INFINITE); + CloseHandle(event); + } + + errcode = clFinish(queue); + test_error(errcode, "Could not finish queue"); + + test_assert_event_complete(signal_event); + + // Release resources + CloseHandle(semaphore_handle); + test_error(clReleaseSemaphoreKHR(semaphore), "Could not release semaphore"); + fence->Release(); + + return TEST_PASS; +} \ No newline at end of file diff --git a/test_conformance/extensions/cl_khr_external_semaphore_dx_fence/test_external_semaphore_dx_fence_negative_wait_signal.cpp b/test_conformance/extensions/cl_khr_external_semaphore_dx_fence/test_external_semaphore_dx_fence_negative_wait_signal.cpp new file mode 100644 index 00000000..6c032c56 --- /dev/null +++ b/test_conformance/extensions/cl_khr_external_semaphore_dx_fence/test_external_semaphore_dx_fence_negative_wait_signal.cpp @@ -0,0 +1,89 @@ +// +// Copyright (c) 2025 The Khronos Group Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#include "semaphore_dx_fence_base.h" + +// Confirm that a wait without a semaphore payload list will return +// CL_INVALID_VALUE +REGISTER_TEST(test_external_semaphores_dx_fence_negative_wait) +{ + int errcode = CL_SUCCESS; + const DirectXWrapper dx_wrapper; + + REQUIRE_EXTENSION("cl_khr_external_semaphore"); + REQUIRE_EXTENSION("cl_khr_external_semaphore_dx_fence"); + + // Obtain pointers to semaphore's API + GET_PFN(device, clCreateSemaphoreWithPropertiesKHR); + GET_PFN(device, clReleaseSemaphoreKHR); + GET_PFN(device, clEnqueueWaitSemaphoresKHR); + + test_error(!is_import_handle_available(device, + CL_SEMAPHORE_HANDLE_D3D12_FENCE_KHR), + "Could not find CL_SEMAPHORE_HANDLE_D3D12_FENCE_KHR between the " + "supported import types"); + + // Import D3D12 fence into OpenCL + const DirectXFenceWrapper fence(dx_wrapper.getDXDevice()); + CLDXSemaphoreWrapper semaphore(device, context, dx_wrapper.getDXDevice()); + test_error(semaphore.createSemaphoreFromFence(*fence), + "Could not create semaphore"); + + log_info("Calling clEnqueueWaitSemaphoresKHR\n"); + errcode = clEnqueueWaitSemaphoresKHR(queue, 1, &semaphore, nullptr, 0, + nullptr, nullptr); + test_assert_error( + errcode == CL_INVALID_VALUE, + "Unexpected error code returned from clEnqueueWaitSemaphores"); + + return TEST_PASS; +} + +// Confirm that a signal without a semaphore payload list will return +// CL_INVALID_VALUE +REGISTER_TEST(test_external_semaphores_dx_fence_negative_signal) +{ + int errcode = CL_SUCCESS; + const DirectXWrapper dx_wrapper; + + REQUIRE_EXTENSION("cl_khr_external_semaphore"); + REQUIRE_EXTENSION("cl_khr_external_semaphore_dx_fence"); + + // Obtain pointers to semaphore's API + GET_PFN(device, clCreateSemaphoreWithPropertiesKHR); + GET_PFN(device, clReleaseSemaphoreKHR); + GET_PFN(device, clEnqueueSignalSemaphoresKHR); + + test_error(!is_import_handle_available(device, + CL_SEMAPHORE_HANDLE_D3D12_FENCE_KHR), + "Could not find CL_SEMAPHORE_HANDLE_D3D12_FENCE_KHR between the " + "supported import types"); + + // Import D3D12 fence into OpenCL + const DirectXFenceWrapper fence(dx_wrapper.getDXDevice()); + CLDXSemaphoreWrapper semaphore(device, context, dx_wrapper.getDXDevice()); + test_error(semaphore.createSemaphoreFromFence(*fence), + "Could not create semaphore"); + + log_info("Calling clEnqueueWaitSemaphoresKHR\n"); + errcode = clEnqueueSignalSemaphoresKHR(queue, 1, &semaphore, nullptr, 0, + nullptr, nullptr); + test_assert_error( + errcode == CL_INVALID_VALUE, + "Unexpected error code returned from clEnqueueSignalSemaphores"); + + return TEST_PASS; +} \ No newline at end of file diff --git a/test_conformance/extensions/cl_khr_external_semaphore_dx_fence/test_external_semaphore_dx_fence_queries.cpp b/test_conformance/extensions/cl_khr_external_semaphore_dx_fence/test_external_semaphore_dx_fence_queries.cpp new file mode 100644 index 00000000..03aa2b15 --- /dev/null +++ b/test_conformance/extensions/cl_khr_external_semaphore_dx_fence/test_external_semaphore_dx_fence_queries.cpp @@ -0,0 +1,69 @@ +// +// Copyright (c) 2025 The Khronos Group Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#include "semaphore_dx_fence_base.h" + +// Confirm that the CL_SEMAPHORE_HANDLE_D3D12_FENCE_KHR property is in the +// properties returned by clGetSemaphoreInfo +REGISTER_TEST(test_external_semaphores_dx_fence_query_properties) +{ + int errcode = CL_SUCCESS; + const DirectXWrapper dx_wrapper; + + REQUIRE_EXTENSION("cl_khr_external_semaphore"); + REQUIRE_EXTENSION("cl_khr_external_semaphore_dx_fence"); + + // Obtain pointers to semaphore's API + GET_PFN(device, clCreateSemaphoreWithPropertiesKHR); + GET_PFN(device, clReleaseSemaphoreKHR); + GET_PFN(device, clGetSemaphoreInfoKHR); + + test_error(!is_import_handle_available(device, + CL_SEMAPHORE_HANDLE_D3D12_FENCE_KHR), + "Could not find CL_SEMAPHORE_HANDLE_D3D12_FENCE_KHR between the " + "supported import types"); + + // Import D3D12 fence into OpenCL + const DirectXFenceWrapper fence(dx_wrapper.getDXDevice()); + CLDXSemaphoreWrapper semaphore(device, context, dx_wrapper.getDXDevice()); + test_error(semaphore.createSemaphoreFromFence(*fence), + "Could not create semaphore"); + + size_t properties_size_bytes = 0; + errcode = clGetSemaphoreInfoKHR(*semaphore, CL_SEMAPHORE_PROPERTIES_KHR, 0, + nullptr, &properties_size_bytes); + test_error(errcode, "Could not get semaphore info"); + std::vector semaphore_properties( + properties_size_bytes / sizeof(cl_semaphore_properties_khr)); + errcode = clGetSemaphoreInfoKHR(*semaphore, CL_SEMAPHORE_PROPERTIES_KHR, + properties_size_bytes, + semaphore_properties.data(), nullptr); + test_error(errcode, "Could not get semaphore info"); + + for (unsigned i = 0; i < semaphore_properties.size() - 1; i++) + { + if (semaphore_properties[i] == CL_SEMAPHORE_HANDLE_D3D12_FENCE_KHR + && semaphore_properties[i + 1] + == reinterpret_cast( + semaphore.getHandle())) + { + return TEST_PASS; + } + } + log_error( + "Failed to find the dx fence handle type in the semaphore properties"); + return TEST_FAIL; +} \ No newline at end of file