cl3 0.9.1

A Rust implementation of the Khronos OpenCL 3.0 API and extensions.
Documentation
// Copyright (c) 2020-2022 Via Technology Ltd.
//
// 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.

//! OpenCL Context API.

#![allow(non_camel_case_types)]
#![allow(clippy::not_unsafe_ptr_arg_deref)]

pub use opencl_sys::{
    cl_context, cl_context_info, cl_context_properties, cl_device_id, cl_device_type, cl_int,
    cl_uint, CL_CONTEXT_DEVICES, CL_CONTEXT_INTEROP_USER_SYNC, CL_CONTEXT_NUM_DEVICES,
    CL_CONTEXT_PLATFORM, CL_CONTEXT_PROPERTIES, CL_CONTEXT_REFERENCE_COUNT, CL_INVALID_VALUE,
    CL_SUCCESS,
};

use opencl_sys::{
    clCreateContext, clCreateContextFromType, clGetContextInfo, clReleaseContext, clRetainContext,
};

#[cfg(feature = "CL_VERSION_3_0")]
use opencl_sys::clSetContextDestructorCallback;

use super::info_type::InfoType;
use super::{api_info_size, api_info_value, api_info_vector};
use libc::{c_char, c_void, intptr_t, size_t};
use std::mem;
use std::ptr;

/// Create an OpenCL context.  
/// Calls clCreateContext to create an OpenCL context.
///
/// * `devices` - a slice of unique devices for an OpenCL platform.
/// * `properties` - a null terminated list of cl_context_properties, see
/// [Context Properties](https://www.khronos.org/registry/OpenCL/specs/3.0-unified/html/OpenCL_API.html#context-properties-table).
/// * `pfn_notify` - an optional callback function that can be registered by the application.
/// * `user_data` - passed as the user_data argument when pfn_notify is called.
///
/// returns a Result containing the new OpenCL context
/// or the error code from the OpenCL C API function.
#[inline]
pub fn create_context(
    devices: &[cl_device_id],
    properties: *const cl_context_properties,
    pfn_notify: Option<unsafe extern "C" fn(*const c_char, *const c_void, size_t, *mut c_void)>,
    user_data: *mut c_void,
) -> Result<cl_context, cl_int> {
    let mut status: cl_int = CL_INVALID_VALUE;
    let context = unsafe {
        clCreateContext(
            properties,
            devices.len() as cl_uint,
            devices.as_ptr(),
            pfn_notify,
            user_data,
            &mut status,
        )
    };
    if CL_SUCCESS != status {
        Err(status)
    } else {
        Ok(context)
    }
}

/// Create an OpenCL context from a specific device type.  
/// Calls clCreateContextFromType to create an OpenCL context.
///
/// * `device_type` - the type of OpenCL device, see:
/// [Device Types](https://www.khronos.org/registry/OpenCL/specs/3.0-unified/html/OpenCL_API.html#device-types-table).
/// * `properties` - a null terminated list of cl_context_properties, see:
/// [Context Properties](https://www.khronos.org/registry/OpenCL/specs/3.0-unified/html/OpenCL_API.html#context-properties-table).
/// * `pfn_notify` - an optional callback function that can be registered by the application.
/// * `user_data` - passed as the user_data argument when pfn_notify is called.
///
/// returns a Result containing the new OpenCL context
/// or the error code from the OpenCL C API function.
#[inline]
pub fn create_context_from_type(
    device_type: cl_device_type,
    properties: *const cl_context_properties,
    pfn_notify: Option<unsafe extern "C" fn(*const c_char, *const c_void, size_t, *mut c_void)>,
    user_data: *mut c_void,
) -> Result<cl_context, cl_int> {
    let mut status: cl_int = CL_INVALID_VALUE;
    let context = unsafe {
        clCreateContextFromType(properties, device_type, pfn_notify, user_data, &mut status)
    };
    if CL_SUCCESS != status {
        Err(status)
    } else {
        Ok(context)
    }
}

/// Retain an OpenCL context.  
/// Calls clRetainContext to increment the context reference count.
///
/// * `context` - the cl_context of the OpenCL context.
///
/// returns an empty Result or the error code from the OpenCL C API function.
///
/// # Safety
///
/// This function is unsafe because it changes the OpenCL object reference count.
#[inline]
pub unsafe fn retain_context(context: cl_context) -> Result<(), cl_int> {
    let status: cl_int = clRetainContext(context);
    if CL_SUCCESS != status {
        Err(status)
    } else {
        Ok(())
    }
}

/// Release an OpenCL context.  
/// Calls clReleaseContext to decrement the context reference count.
///
/// * `context` - the cl_context of the OpenCL context.
///
/// returns an empty Result or the error code from the OpenCL C API function.
///
/// # Safety
///
/// This function is unsafe because it changes the OpenCL object reference count.
#[inline]
pub unsafe fn release_context(context: cl_context) -> Result<(), cl_int> {
    let status: cl_int = clReleaseContext(context);
    if CL_SUCCESS != status {
        Err(status)
    } else {
        Ok(())
    }
}

/// Get data about an OpenCL context.
/// Calls clGetContextInfo to get the desired data about the context.
pub fn get_context_data(
    context: cl_context,
    param_name: cl_context_info,
) -> Result<Vec<u8>, cl_int> {
    api_info_size!(get_size, clGetContextInfo);
    let size = get_size(context, param_name)?;
    api_info_vector!(get_vector, u8, clGetContextInfo);
    get_vector(context, param_name, size)
}

/// Get specific information about an OpenCL context.  
/// Calls clGetContextInfo to get the desired information about the context.
///
/// * `context` - the cl_context of the OpenCL context.
/// * `param_name` - the type of platform information being queried, see:
/// [Context Attributes](https://www.khronos.org/registry/OpenCL/specs/3.0-unified/html/OpenCL_API.html#context-info-table).
///
/// returns a Result containing the desired information in an InfoType enum
/// or the error code from the OpenCL C API function.
pub fn get_context_info(
    context: cl_context,
    param_name: cl_context_info,
) -> Result<InfoType, cl_int> {
    api_info_size!(get_size, clGetContextInfo);

    match param_name {
        CL_CONTEXT_REFERENCE_COUNT | CL_CONTEXT_NUM_DEVICES => {
            api_info_value!(get_value, cl_uint, clGetContextInfo);
            Ok(InfoType::Uint(get_value(context, param_name)?))
        }

        CL_CONTEXT_DEVICES | CL_CONTEXT_PROPERTIES => {
            api_info_vector!(get_vec, intptr_t, clGetContextInfo);
            let size = get_size(context, param_name)?;
            Ok(InfoType::VecIntPtr(get_vec(context, param_name, size)?))
        }

        _ => Ok(InfoType::VecUchar(get_context_data(context, param_name)?)),
    }
}

// #ifdef CL_VERSION_3_0
/// Register a callback function with a context that is called when the context is destroyed.  
/// Calls clSetContextDestructorCallback.  
/// CL_VERSION_3_0
///
/// * `context` - the cl_context of the OpenCL context.
/// * `pfn_notify` - callback function to be registered by the application.
/// * `user_data` - passed as the user_data argument when pfn_notify is called.
///
/// returns an empty Result or the error code from the OpenCL C API function.
#[cfg(feature = "CL_VERSION_3_0")]
#[inline]
pub fn set_context_destructor_callback(
    context: cl_context,
    pfn_notify: Option<unsafe extern "C" fn(cl_context, *mut c_void)>,
    user_data: *mut c_void,
) -> Result<(), cl_int> {
    let status: cl_int = unsafe { clSetContextDestructorCallback(context, pfn_notify, user_data) };
    if CL_SUCCESS != status {
        Err(status)
    } else {
        Ok(())
    }
}
// #endif

#[cfg(test)]
mod tests {
    use super::*;
    use crate::device::{get_device_ids, CL_DEVICE_TYPE_GPU};
    use crate::platform::get_platform_ids;

    #[test]
    fn test_context() {
        let platform_ids = get_platform_ids().unwrap();

        // Choose the first platform
        let platform_id = platform_ids[0];

        let device_ids = get_device_ids(platform_id, CL_DEVICE_TYPE_GPU).unwrap();
        assert!(0 < device_ids.len());

        let context = create_context(&device_ids, ptr::null(), None, ptr::null_mut());
        let context = context.unwrap();

        let value = get_context_info(context, CL_CONTEXT_REFERENCE_COUNT).unwrap();
        let value = cl_uint::from(value);
        println!("CL_CONTEXT_REFERENCE_COUNT: {}", value);
        assert!(0 < value);

        let value = get_context_info(context, CL_CONTEXT_DEVICES).unwrap();
        let value = Vec::<intptr_t>::from(value);
        println!("CL_CONTEXT_DEVICES: {}", value.len());
        println!("CL_CONTEXT_DEVICES: {:?}", value);
        assert!(0 < value.len());

        let value = get_context_info(context, CL_CONTEXT_PROPERTIES).unwrap();
        let value = Vec::<intptr_t>::from(value);
        println!("CL_CONTEXT_PROPERTIES: {}", value.len());
        println!("CL_CONTEXT_PROPERTIES: {:?}", value);
        // assert!(0 < value.len());

        let value = get_context_info(context, CL_CONTEXT_NUM_DEVICES).unwrap();
        let value = cl_uint::from(value);
        println!("CL_CONTEXT_NUM_DEVICES: {}", value);
        assert!(0 < value);

        unsafe {
            release_context(context).unwrap();
        }
    }
}