Add ComputeManager; add SmoHooks for getting ClDevices, buffers

We added a new centralized OpenCL Compute manager. This can later
be extended to support CUDA, SyCL, etc. SMO can be configured at
build time to choose which API it will use for compute.

Moreover, the ComputeMgr allows us to register buffers which are
available to all cl_contexts.
This commit is contained in:
2025-11-19 22:33:30 -04:00
parent a910909ad5
commit 27b43c6686
9 changed files with 665 additions and 45 deletions
+44
View File
@@ -116,6 +116,50 @@ find_package(PkgConfig REQUIRED)
find_package(FLEX REQUIRED) find_package(FLEX REQUIRED)
find_package(BISON REQUIRED) find_package(BISON REQUIRED)
# Find OpenCL 1.2 or higher: try find_package first, fall back to pkg-config
find_package(OpenCL 1.2 QUIET)
if(OpenCL_FOUND)
# Normalize find_package variables to match pkg_check_modules naming
set(OPENCL_FOUND TRUE)
set(OPENCL_INCLUDE_DIRS ${OpenCL_INCLUDE_DIRS})
# Handle both OpenCL_LIBRARY (singular) and OpenCL_LIBRARIES (plural)
if(OpenCL_LIBRARIES)
set(OPENCL_LIBRARIES ${OpenCL_LIBRARIES})
else()
set(OPENCL_LIBRARIES ${OpenCL_LIBRARY})
endif()
set(OPENCL_LIBRARY_DIRS "")
message(STATUS "Found OpenCL using find_package")
# Check if version is available and validate
if(OpenCL_VERSION)
if(OpenCL_VERSION VERSION_LESS "1.2")
message(FATAL_ERROR
"OpenCL version ${OpenCL_VERSION} found, but 1.2 or higher is required")
endif()
message(STATUS "OpenCL version: ${OpenCL_VERSION}")
else()
message(WARNING
"OpenCL version could not be determined. "
"Version 1.2+ is required at runtime.")
endif()
else()
# Fall back to pkg-config
pkg_check_modules(OPENCL OpenCL)
if(NOT OPENCL_FOUND)
message(FATAL_ERROR
"Failed to find OpenCL: both find_package and "
"pkg_check_modules failed. Try installing the "
"'ocl-icd-opencl-dev' package (or the appropriate "
"OpenCL development package for your system)."
)
endif()
message(STATUS "Found OpenCL using pkg-config")
message(WARNING
"OpenCL version could not be determined via pkg-config. "
"Version 1.2+ is required at runtime.")
endif()
# Need dlopen() and dlsym() # Need dlopen() and dlsym()
find_library(DL_LIBRARY NAMES dl ldl) find_library(DL_LIBRARY NAMES dl ldl)
if(NOT DL_LIBRARY) if(NOT DL_LIBRARY)
+120
View File
@@ -0,0 +1,120 @@
#ifndef _USER_COMPUTE_H
#define _USER_COMPUTE_H
#include <memory>
#include <vector>
#define CL_TARGET_OPENCL_VERSION 120
#include <CL/cl.h>
namespace smo {
namespace compute {
/**
* @brief OpenCL compute device information
*
* Manages a single OpenCL device, creating and owning its context and command
* queue.
*/
class ComputeDevice
{
public:
/**
* @brief Construct a ComputeDevice from platform and device IDs
*
* Creates the OpenCL context and command queue for the device.
* Throws std::runtime_error if context or queue creation fails.
*
* @param platformId OpenCL platform ID
* @param deviceId OpenCL device ID
*/
ComputeDevice(cl_platform_id platformId, cl_device_id deviceId);
~ComputeDevice()
{
if (commandQueue)
{
clReleaseCommandQueue(commandQueue);
commandQueue = nullptr;
}
if (context)
{
clReleaseContext(context);
context = nullptr;
}
}
// Non-copyable
ComputeDevice(const ComputeDevice&) = delete;
ComputeDevice& operator=(const ComputeDevice&) = delete;
cl_platform_id platform;
cl_device_id device;
cl_context context;
cl_command_queue commandQueue;
};
/**
* @brief Association between an OpenCL buffer and a compute device
*/
struct ClBufferDeviceAssociation
{
ClBufferDeviceAssociation(
cl_mem buf, const std::shared_ptr<ComputeDevice>& dev)
: buffer(buf), device(dev)
{}
cl_mem buffer;
std::shared_ptr<ComputeDevice> device;
};
/**
* @brief OpenCL buffer created on all compute devices
*
* Manages a USE_HOST_PTR buffer created on all available compute devices.
* The constructor creates buffers for all devices, and the destructor releases
* them.
*/
class ClBuffer
{
public:
/**
* @brief Construct a ClBuffer and create buffers on all devices
*
* Creates a USE_HOST_PTR buffer on each device's context.
* Throws std::runtime_error if buffer creation fails for any device.
*
* @param hostPtr Host pointer to use
* @param size Size of buffer in bytes
* @param flags Additional OpenCL memory flags
* @param devices Vector of compute devices to create buffers on
*/
ClBuffer(
void* hostPtr, size_t size, cl_mem_flags flags,
const std::vector<std::shared_ptr<ComputeDevice>>& devices);
~ClBuffer()
{
for (auto& assoc : associations)
{
if (assoc.buffer)
{
clReleaseMemObject(assoc.buffer);
assoc.buffer = nullptr;
}
}
}
// Non-copyable
ClBuffer(const ClBuffer&) = delete;
ClBuffer& operator=(const ClBuffer&) = delete;
void* hostPtr;
size_t size;
cl_mem_flags flags;
std::vector<ClBufferDeviceAssociation> associations;
};
} // namespace compute
} // namespace smo
#endif // _USER_COMPUTE_H
+40
View File
@@ -6,9 +6,12 @@
#include <string> #include <string>
#include <functional> #include <functional>
#include <memory> #include <memory>
#include <vector>
#include <preprocessor.h> #include <preprocessor.h>
#include <user/deviceAttachmentSpec.h> #include <user/deviceAttachmentSpec.h>
#include <callback.h> #include <callback.h>
#define CL_TARGET_OPENCL_VERSION 120
#include <CL/cl.h>
class OptionParser; class OptionParser;
@@ -16,6 +19,11 @@ namespace smo {
class ComponentThread; class ComponentThread;
namespace compute {
class ClBuffer;
class ComputeDevice;
} // namespace compute
namespace stim_buff { namespace stim_buff {
/** /**
@@ -92,6 +100,38 @@ struct SmoCallbacks
* equivalent to calling OptionParser::getOptions(). * equivalent to calling OptionParser::getOptions().
*/ */
OptionParser& (*OptionParser_getOptions)(void); OptionParser& (*OptionParser_getOptions)(void);
/**
* @brief Create a USE_HOST_PTR buffer on all OpenCL contexts
* @param hostPtr Host pointer to the memory
* @param size Size of the buffer in bytes
* @param flags Additional OpenCL memory flags
* @return Shared pointer to ClBuffer managing buffers on all devices
*/
std::shared_ptr<smo::compute::ClBuffer>
(*ComputeManager_createUseHostPtrBuffer)(
void* hostPtr, size_t size, cl_mem_flags flags);
/**
* @brief Release USE_HOST_PTR buffers from all contexts
* @param buffer Shared pointer to ClBuffer to release
*/
void (*ComputeManager_releaseUseHostPtrBuffer)(
std::shared_ptr<smo::compute::ClBuffer> buffer);
/**
* @brief Get a compute device
* @return Shared pointer to ComputeDevice, or nullptr if no devices available
*/
std::shared_ptr<smo::compute::ComputeDevice>
(*ComputeManager_getDevice)(void);
/**
* @brief Release a compute device
* @param device Shared pointer to ComputeDevice to release
*/
void (*ComputeManager_releaseDevice)(
std::shared_ptr<smo::compute::ComputeDevice> device);
}; };
struct Sal_Mgmt_LibOps struct Sal_Mgmt_LibOps
+8
View File
@@ -34,6 +34,9 @@ add_library(smocore STATIC
# SenseApis # SenseApis
stimBuffApis/stimBuffApiManager.cpp stimBuffApis/stimBuffApiManager.cpp
# ComputeManager
computeManager/computeManager.cpp
# MindManager # MindManager
mindManager/mindManager.cpp mindManager/mindManager.cpp
) )
@@ -47,6 +50,7 @@ target_include_directories(smocore PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}/include ${CMAKE_CURRENT_SOURCE_DIR}/include
${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_BINARY_DIR}
${Boost_INCLUDE_DIRS} ${Boost_INCLUDE_DIRS}
${OPENCL_INCLUDE_DIRS}
) )
# Link against pthread for CPU affinity functions # Link against pthread for CPU affinity functions
@@ -55,4 +59,8 @@ target_link_libraries(smocore PRIVATE
Threads::Threads Threads::Threads
Boost::system Boost::system
Boost::log Boost::log
${OPENCL_LIBRARIES}
)
target_link_directories(smocore PRIVATE
${OPENCL_LIBRARY_DIRS}
) )
+307
View File
@@ -0,0 +1,307 @@
#include <computeManager/computeManager.h>
#include <iostream>
#include <stdexcept>
#include <string>
#include <string_view>
#include <algorithm>
namespace smo {
namespace compute {
// Helper function to parse OpenCL version string
static std::pair<int, int> parseOpenClVersion(const std::string& versionStr)
{
size_t spacePos = versionStr.find(' ');
if (spacePos == std::string::npos) { return {-1, -1}; }
std::string versionNum = versionStr.substr(spacePos + 1);
size_t dotPos = versionNum.find('.');
if (dotPos == std::string::npos) { return {-1, -1}; }
try {
int major = std::stoi(versionNum.substr(0, dotPos));
int minor = std::stoi(versionNum.substr(dotPos + 1));
return {major, minor};
} catch (const std::exception&) {
return {-1, -1};
}
}
// Helper function to validate OpenCL version
static bool validateOpenClVersion(
std::string_view versionStr, std::string_view versionType,
int minMajor, int minMinor)
{
auto [major, minor] = parseOpenClVersion(std::string(versionStr));
if (major == -1 && minor == -1)
{
std::cerr << __func__ << ": failed to parse OpenCL " << versionType
<< " version: " << versionStr << std::endl;
return false;
}
if (major < minMajor || (major == minMajor && minor < minMinor))
{
std::cerr << __func__ << ": OpenCL " << versionType << " version "
<< major << "." << minor << " found, but " << minMajor << "."
<< minMinor << " or higher is required" << std::endl;
return false;
}
std::cout << __func__ << ": OpenCL " << versionType << " version: "
<< versionStr << std::endl;
return true;
}
ComputeDevice::ComputeDevice(cl_platform_id platformId, cl_device_id deviceId)
: platform(platformId), device(deviceId),
context(nullptr), commandQueue(nullptr)
{
cl_int err;
// Create context for this device
context = clCreateContext(
nullptr, 1, &device,
nullptr, nullptr, &err);
if (err != CL_SUCCESS || !context)
{
throw std::runtime_error(
std::string(__func__) + ": failed to create context for device: " +
std::to_string(err));
}
// Create command queue
cl_command_queue_properties queueProps = 0;
commandQueue = clCreateCommandQueue(
context, device, queueProps, &err);
if (err != CL_SUCCESS || !commandQueue)
{
clReleaseContext(context);
context = nullptr;
throw std::runtime_error(
std::string(__func__) + ": failed to create command queue for "
"device: " + std::to_string(err));
}
}
void ComputeManager::initialize()
{
if (initialized) { return; }
cl_int err;
// Get number of platforms
cl_uint numPlatforms = 0;
err = clGetPlatformIDs(0, nullptr, &numPlatforms);
if (err != CL_SUCCESS)
{
throw std::runtime_error(
std::string(__func__) + ": failed to get OpenCL platforms: " +
std::to_string(err));
}
if (numPlatforms == 0)
{
throw std::runtime_error(
std::string(__func__) + ": no OpenCL platforms found");
}
// Get all platforms
std::vector<cl_platform_id> platforms(numPlatforms);
err = clGetPlatformIDs(numPlatforms, platforms.data(), nullptr);
if (err != CL_SUCCESS)
{
throw std::runtime_error(
std::string(__func__) + ": failed to enumerate OpenCL platforms: " +
std::to_string(err));
}
// Enumerate devices for each platform
for (cl_uint p = 0; p < numPlatforms; ++p)
{
cl_platform_id platform = platforms[p];
// Check platform version
char platformVersion[128];
err = clGetPlatformInfo(
platform, CL_PLATFORM_VERSION,
sizeof(platformVersion), platformVersion, nullptr);
if (err == CL_SUCCESS)
{
if (!validateOpenClVersion(platformVersion, "platform", 1, 2))
{
std::cout << __func__ << ": skipping platform " << p
<< " with incompatible OpenCL version "
<< std::string(platformVersion) << std::endl;
continue;
}
}
// Get number of devices
cl_uint numDevices = 0;
err = clGetDeviceIDs(
platform, CL_DEVICE_TYPE_ALL, 0, nullptr, &numDevices);
if (err != CL_SUCCESS || numDevices == 0)
{
std::cout << __func__ << ": skipping platform " << p
<< " with no devices" << std::endl;
continue;
}
// Get all devices
std::vector<cl_device_id> platformDevices(numDevices);
err = clGetDeviceIDs(
platform, CL_DEVICE_TYPE_ALL, numDevices,
platformDevices.data(), nullptr);
if (err != CL_SUCCESS)
{
throw std::runtime_error(
std::string(__func__) + ": failed to enumerate devices for "
"platform " + std::to_string(p) + ": " + std::to_string(err));
}
// Create ComputeDevice for each device
for (cl_uint d = 0; d < numDevices; ++d)
{
cl_device_id device = platformDevices[d];
// Check device version
char deviceVersion[128];
err = clGetDeviceInfo(
device, CL_DEVICE_VERSION,
sizeof(deviceVersion), deviceVersion, nullptr);
if (err == CL_SUCCESS)
{
if (!validateOpenClVersion(deviceVersion, "device", 1, 2))
{
std::cout << __func__ << ": skipping device " << d
<< " with incompatible OpenCL version "
<< std::string(deviceVersion) << std::endl;
continue;
}
}
// Create ComputeDevice (constructor creates context and queue)
try
{
auto deviceObj = std::make_shared<ComputeDevice>(
platform, device);
devices.push_back(deviceObj);
}
catch (const std::runtime_error& e)
{
// Re-throw with more context about which device/platform
throw std::runtime_error(
std::string(__func__) + ": failed to create ComputeDevice "
"for device " + std::to_string(d) + " on platform " +
std::to_string(p) + ": " + e.what());
}
}
}
if (devices.empty())
{
throw std::runtime_error(
std::string(__func__) + ": no compatible OpenCL devices found");
}
initialized = true;
std::cout << __func__ << ": Initialized with " << devices.size()
<< " compute device(s)" << std::endl;
}
void ComputeManager::finalize()
{
if (!initialized) { return; }
// Release all devices (their shared_ptrs will clean up contexts/queues)
devices.clear();
initialized = false;
std::cout << __func__ << ": Finalized" << std::endl;
}
ClBuffer::ClBuffer(void* hostPtr, size_t size, cl_mem_flags flags,
const std::vector<std::shared_ptr<ComputeDevice>>& devices)
: hostPtr(hostPtr), size(size), flags(flags)
{
associations.reserve(devices.size());
// Create a buffer for each device's context
for (const auto& device : devices)
{
if (!device->context) { continue; }
cl_int err;
cl_mem_flags bufferFlags = CL_MEM_USE_HOST_PTR | flags;
cl_mem buffer = clCreateBuffer(
device->context,
bufferFlags,
size, hostPtr,
&err);
if (err != CL_SUCCESS || !buffer)
{
// Release any buffers already created before throwing
for (auto& assoc : associations)
{
if (assoc.buffer) {
clReleaseMemObject(assoc.buffer);
}
}
throw std::runtime_error(
std::string(__func__) + ": failed to create buffer for "
"device: " + std::to_string(err));
}
associations.emplace_back(buffer, device);
}
}
std::shared_ptr<ClBuffer>
ComputeManager::createUseHostPtrBuffer(
void* hostPtr, size_t size, cl_mem_flags flags)
{
if (!initialized)
{
std::cerr << __func__ << ": ComputeManager not initialized"
<< std::endl;
throw std::runtime_error(
std::string(__func__) + ": ComputeManager not initialized");
}
return std::make_shared<ClBuffer>(hostPtr, size, flags, devices);
}
void ComputeManager::releaseUseHostPtrBuffer(std::shared_ptr<ClBuffer> buffer)
{
// No-op: ClBuffer's destructor handles cleanup automatically
// This function exists for API compatibility
(void)buffer;
}
std::shared_ptr<ComputeDevice> ComputeManager::getDevice()
{
if (!initialized || devices.empty()) {
return nullptr;
}
// Return first available device
// In the future, this will filter based on ComputeDeviceConstraints
return devices[0];
}
void ComputeManager::releaseDevice(std::shared_ptr<ComputeDevice> device)
{
// Placeholder for future refcounting implementation
// Devices are only removed in finalize()
(void)device;
}
} // namespace compute
} // namespace smo
@@ -0,0 +1,105 @@
#ifndef _COMPUTE_MANAGER_H
#define _COMPUTE_MANAGER_H
#include <memory>
#include <vector>
#include <user/compute.h>
namespace smo {
namespace compute {
/**
* @brief Centralized OpenCL platform and device management
*
* Enumerates all OpenCL platforms and devices, maintains contexts and command
* queues, and provides methods to create buffers and access devices.
*/
class ComputeManager
{
public:
static ComputeManager& getInstance()
{
static ComputeManager instance;
return instance;
}
/**
* @brief Initialize ComputeManager by enumerating platforms and devices
*
* Enumerates all OpenCL platforms, then all devices on each platform,
* creating contexts and command queues for each device.
* Idempotent - can be called multiple times safely.
*/
void initialize();
/**
* @brief Finalize ComputeManager, releasing all resources
*
* Releases all contexts, command queues, and clears device list.
* Safe to call even if not initialized.
*/
void finalize();
/**
* @brief Create USE_HOST_PTR buffers on all contexts
*
* Creates a buffer using CL_MEM_USE_HOST_PTR on each device's context.
*
* @param hostPtr Host pointer to use
* @param size Size of buffer in bytes
* @param flags Additional OpenCL memory flags
* @return Shared pointer to ClBuffer managing buffers on all devices
*/
std::shared_ptr<ClBuffer> createUseHostPtrBuffer(
void* hostPtr, size_t size, cl_mem_flags flags);
/**
* @brief Release USE_HOST_PTR buffers
*
* Releases all buffers. This is a no-op since ClBuffer's destructor
* handles cleanup automatically.
*
* @param buffer Shared pointer to ClBuffer to release
*/
void releaseUseHostPtrBuffer(std::shared_ptr<ClBuffer> buffer);
/**
* @brief Get a compute device
*
* Returns the first available device. Later will accept
* ComputeDeviceConstraints for filtering.
*
* @return Shared pointer to ComputeDevice, or nullptr if no devices available
*/
std::shared_ptr<ComputeDevice> getDevice();
/**
* @brief Release a compute device
*
* Placeholder for future refcounting implementation.
* Currently a no-op - devices are only removed in finalize().
*
* @param device Shared pointer to ComputeDevice to release
*/
void releaseDevice(std::shared_ptr<ComputeDevice> device);
private:
ComputeManager() : initialized(false) {}
~ComputeManager() {}
// Non-copyable, non-movable
ComputeManager(const ComputeManager&) = delete;
ComputeManager& operator=(const ComputeManager&) = delete;
ComputeManager(ComputeManager&&) = delete;
ComputeManager& operator=(ComputeManager&&) = delete;
bool initialized;
std::vector<std::shared_ptr<ComputeDevice>> devices;
};
} // namespace compute
} // namespace smo
#endif // _COMPUTE_MANAGER_H
+3
View File
@@ -2,6 +2,7 @@
#include <mindManager/mindManager.h> #include <mindManager/mindManager.h>
#include <deviceManager/deviceManager.h> #include <deviceManager/deviceManager.h>
#include <stimBuffApis/stimBuffApiManager.h> #include <stimBuffApis/stimBuffApiManager.h>
#include <computeManager/computeManager.h>
#include <salmanoff.h> #include <salmanoff.h>
@@ -11,6 +12,7 @@ void initializeSalmanoff(void)
{ {
std::cout << __func__ << ": Entered." << std::endl; std::cout << __func__ << ": Entered." << std::endl;
compute::ComputeManager::getInstance().initialize();
mind::MindManager::getInstance().initialize(); mind::MindManager::getInstance().initialize();
stim_buff::StimBuffApiManager::getInstance().initialize(); stim_buff::StimBuffApiManager::getInstance().initialize();
device::DeviceManager::getInstance().initialize(); device::DeviceManager::getInstance().initialize();
@@ -25,6 +27,7 @@ void shutdownSalmanoff(void)
device::DeviceManager::getInstance().finalize(); device::DeviceManager::getInstance().finalize();
stim_buff::StimBuffApiManager::getInstance().finalize(); stim_buff::StimBuffApiManager::getInstance().finalize();
mind::MindManager::getInstance().finalize(); mind::MindManager::getInstance().finalize();
compute::ComputeManager::getInstance().finalize();
} }
} // namespace smo } // namespace smo
+38 -1
View File
@@ -13,6 +13,7 @@
#include <mind.h> #include <mind.h>
#include <deviceManager/deviceManager.h> #include <deviceManager/deviceManager.h>
#include <marionette/marionette.h> #include <marionette/marionette.h>
#include <computeManager/computeManager.h>
namespace fs = std::filesystem; namespace fs = std::filesystem;
@@ -85,6 +86,36 @@ static OptionParser& OptionParser_getOptions()
return OptionParser::getOptions(); return OptionParser::getOptions();
} }
/* Local static functions to wrap ComputeManager methods for SmoCallbacks */
static std::shared_ptr<smo::compute::ClBuffer>
ComputeManager_createUseHostPtrBuffer(
void* hostPtr, size_t size, cl_mem_flags flags
)
{
return smo::compute::ComputeManager::getInstance().createUseHostPtrBuffer(
hostPtr, size, flags);
}
static void ComputeManager_releaseUseHostPtrBuffer(
std::shared_ptr<smo::compute::ClBuffer> buffer
)
{
smo::compute::ComputeManager::getInstance().releaseUseHostPtrBuffer(
buffer);
}
static std::shared_ptr<smo::compute::ComputeDevice> ComputeManager_getDevice()
{
return smo::compute::ComputeManager::getInstance().getDevice();
}
static void ComputeManager_releaseDevice(
std::shared_ptr<smo::compute::ComputeDevice> device
)
{
smo::compute::ComputeManager::getInstance().releaseDevice(device);
}
/* Hooks to be provided to stimBuffApiLibs, enabling them to call into Salmanoff /* Hooks to be provided to stimBuffApiLibs, enabling them to call into Salmanoff
* code. * code.
*/ */
@@ -92,7 +123,13 @@ static SmoCallbacks smoCallbacks =
{ {
.searchForLibInSmoSearchPaths = searchForLibInSmoSearchPaths, .searchForLibInSmoSearchPaths = searchForLibInSmoSearchPaths,
.ComponentThread_getSelf = ComponentThread_getSelf, .ComponentThread_getSelf = ComponentThread_getSelf,
.OptionParser_getOptions = OptionParser_getOptions .OptionParser_getOptions = OptionParser_getOptions,
.ComputeManager_createUseHostPtrBuffer =
ComputeManager_createUseHostPtrBuffer,
.ComputeManager_releaseUseHostPtrBuffer =
ComputeManager_releaseUseHostPtrBuffer,
.ComputeManager_getDevice = ComputeManager_getDevice,
.ComputeManager_releaseDevice = ComputeManager_releaseDevice
}; };
/* Static file-scope threading model object for senseApi libraries */ /* Static file-scope threading model object for senseApi libraries */
-44
View File
@@ -9,50 +9,6 @@ if(ENABLE_STIMBUFFAPI_livoxGen1)
# Find liburing using pkg-config # Find liburing using pkg-config
pkg_check_modules(URING REQUIRED liburing) pkg_check_modules(URING REQUIRED liburing)
# Find OpenCL 1.2 or higher: try find_package first, fall back to pkg-config
find_package(OpenCL 1.2 QUIET)
if(OpenCL_FOUND)
# Normalize find_package variables to match pkg_check_modules naming
set(OPENCL_FOUND TRUE)
set(OPENCL_INCLUDE_DIRS ${OpenCL_INCLUDE_DIRS})
# Handle both OpenCL_LIBRARY (singular) and OpenCL_LIBRARIES (plural)
if(OpenCL_LIBRARIES)
set(OPENCL_LIBRARIES ${OpenCL_LIBRARIES})
else()
set(OPENCL_LIBRARIES ${OpenCL_LIBRARY})
endif()
set(OPENCL_LIBRARY_DIRS "")
message(STATUS "Found OpenCL using find_package")
# Check if version is available and validate
if(OpenCL_VERSION)
if(OpenCL_VERSION VERSION_LESS "1.2")
message(FATAL_ERROR
"OpenCL version ${OpenCL_VERSION} found, but 1.2 or higher is required")
endif()
message(STATUS "OpenCL version: ${OpenCL_VERSION}")
else()
message(WARNING
"OpenCL version could not be determined. "
"Version 1.2+ is required at runtime.")
endif()
else()
# Fall back to pkg-config
pkg_check_modules(OPENCL OpenCL)
if(NOT OPENCL_FOUND)
message(FATAL_ERROR
"Failed to find OpenCL: both find_package and "
"pkg_check_modules failed. Try installing the "
"'ocl-icd-opencl-dev' package (or the appropriate "
"OpenCL development package for your system)."
)
endif()
message(STATUS "Found OpenCL using pkg-config")
message(WARNING
"OpenCL version could not be determined via pkg-config. "
"Version 1.2+ is required at runtime.")
endif()
# Enable assembly language # Enable assembly language
enable_language(ASM) enable_language(ASM)