diff --git a/CMakeLists.txt b/CMakeLists.txt index 475fc53..5ffa58e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -116,6 +116,50 @@ find_package(PkgConfig REQUIRED) find_package(FLEX 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() find_library(DL_LIBRARY NAMES dl ldl) if(NOT DL_LIBRARY) diff --git a/include/user/compute.h b/include/user/compute.h new file mode 100644 index 0000000..f956b67 --- /dev/null +++ b/include/user/compute.h @@ -0,0 +1,120 @@ +#ifndef _USER_COMPUTE_H +#define _USER_COMPUTE_H + +#include +#include +#define CL_TARGET_OPENCL_VERSION 120 +#include + +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& dev) + : buffer(buf), device(dev) + {} + + cl_mem buffer; + std::shared_ptr 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>& 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 associations; +}; + +} // namespace compute +} // namespace smo + +#endif // _USER_COMPUTE_H diff --git a/include/user/senseApiDesc.h b/include/user/senseApiDesc.h index 23e2145..93d73c5 100644 --- a/include/user/senseApiDesc.h +++ b/include/user/senseApiDesc.h @@ -6,9 +6,12 @@ #include #include #include +#include #include #include #include +#define CL_TARGET_OPENCL_VERSION 120 +#include class OptionParser; @@ -16,6 +19,11 @@ namespace smo { class ComponentThread; +namespace compute { +class ClBuffer; +class ComputeDevice; +} // namespace compute + namespace stim_buff { /** @@ -92,6 +100,38 @@ struct SmoCallbacks * equivalent to calling OptionParser::getOptions(). */ 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 + (*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 buffer); + + /** + * @brief Get a compute device + * @return Shared pointer to ComputeDevice, or nullptr if no devices available + */ + std::shared_ptr + (*ComputeManager_getDevice)(void); + + /** + * @brief Release a compute device + * @param device Shared pointer to ComputeDevice to release + */ + void (*ComputeManager_releaseDevice)( + std::shared_ptr device); }; struct Sal_Mgmt_LibOps diff --git a/smocore/CMakeLists.txt b/smocore/CMakeLists.txt index 92aeb18..13d105e 100644 --- a/smocore/CMakeLists.txt +++ b/smocore/CMakeLists.txt @@ -34,6 +34,9 @@ add_library(smocore STATIC # SenseApis stimBuffApis/stimBuffApiManager.cpp + # ComputeManager + computeManager/computeManager.cpp + # MindManager mindManager/mindManager.cpp ) @@ -47,6 +50,7 @@ target_include_directories(smocore PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include ${CMAKE_CURRENT_BINARY_DIR} ${Boost_INCLUDE_DIRS} + ${OPENCL_INCLUDE_DIRS} ) # Link against pthread for CPU affinity functions @@ -55,4 +59,8 @@ target_link_libraries(smocore PRIVATE Threads::Threads Boost::system Boost::log + ${OPENCL_LIBRARIES} +) +target_link_directories(smocore PRIVATE + ${OPENCL_LIBRARY_DIRS} ) diff --git a/smocore/computeManager/computeManager.cpp b/smocore/computeManager/computeManager.cpp new file mode 100644 index 0000000..46870b6 --- /dev/null +++ b/smocore/computeManager/computeManager.cpp @@ -0,0 +1,307 @@ +#include +#include +#include +#include +#include +#include + +namespace smo { +namespace compute { + +// Helper function to parse OpenCL version string +static std::pair 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 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 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( + 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>& 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 +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(hostPtr, size, flags, devices); +} + +void ComputeManager::releaseUseHostPtrBuffer(std::shared_ptr buffer) +{ + // No-op: ClBuffer's destructor handles cleanup automatically + // This function exists for API compatibility + (void)buffer; +} + +std::shared_ptr 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 device) +{ + // Placeholder for future refcounting implementation + // Devices are only removed in finalize() + (void)device; +} + +} // namespace compute +} // namespace smo diff --git a/smocore/include/computeManager/computeManager.h b/smocore/include/computeManager/computeManager.h new file mode 100644 index 0000000..402e41f --- /dev/null +++ b/smocore/include/computeManager/computeManager.h @@ -0,0 +1,105 @@ +#ifndef _COMPUTE_MANAGER_H +#define _COMPUTE_MANAGER_H + +#include +#include +#include + +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 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 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 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 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> devices; +}; + +} // namespace compute +} // namespace smo + +#endif // _COMPUTE_MANAGER_H + + diff --git a/smocore/marionette/salmanoff.cpp b/smocore/marionette/salmanoff.cpp index 47e2964..8b7e6f5 100644 --- a/smocore/marionette/salmanoff.cpp +++ b/smocore/marionette/salmanoff.cpp @@ -2,6 +2,7 @@ #include #include #include +#include #include @@ -11,6 +12,7 @@ void initializeSalmanoff(void) { std::cout << __func__ << ": Entered." << std::endl; + compute::ComputeManager::getInstance().initialize(); mind::MindManager::getInstance().initialize(); stim_buff::StimBuffApiManager::getInstance().initialize(); device::DeviceManager::getInstance().initialize(); @@ -25,6 +27,7 @@ void shutdownSalmanoff(void) device::DeviceManager::getInstance().finalize(); stim_buff::StimBuffApiManager::getInstance().finalize(); mind::MindManager::getInstance().finalize(); + compute::ComputeManager::getInstance().finalize(); } } // namespace smo diff --git a/smocore/stimBuffApis/stimBuffApiManager.cpp b/smocore/stimBuffApis/stimBuffApiManager.cpp index a2b2805..1b319b5 100644 --- a/smocore/stimBuffApis/stimBuffApiManager.cpp +++ b/smocore/stimBuffApis/stimBuffApiManager.cpp @@ -13,6 +13,7 @@ #include #include #include +#include namespace fs = std::filesystem; @@ -85,6 +86,36 @@ static OptionParser& OptionParser_getOptions() return OptionParser::getOptions(); } +/* Local static functions to wrap ComputeManager methods for SmoCallbacks */ +static std::shared_ptr +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 buffer + ) +{ + smo::compute::ComputeManager::getInstance().releaseUseHostPtrBuffer( + buffer); +} + +static std::shared_ptr ComputeManager_getDevice() +{ + return smo::compute::ComputeManager::getInstance().getDevice(); +} + +static void ComputeManager_releaseDevice( + std::shared_ptr device + ) +{ + smo::compute::ComputeManager::getInstance().releaseDevice(device); +} + /* Hooks to be provided to stimBuffApiLibs, enabling them to call into Salmanoff * code. */ @@ -92,7 +123,13 @@ static SmoCallbacks smoCallbacks = { .searchForLibInSmoSearchPaths = searchForLibInSmoSearchPaths, .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 */ diff --git a/stimBuffApis/livoxGen1/CMakeLists.txt b/stimBuffApis/livoxGen1/CMakeLists.txt index 11d7f5f..26505af 100644 --- a/stimBuffApis/livoxGen1/CMakeLists.txt +++ b/stimBuffApis/livoxGen1/CMakeLists.txt @@ -9,50 +9,6 @@ if(ENABLE_STIMBUFFAPI_livoxGen1) # Find liburing using pkg-config 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_language(ASM)