1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
// Copyright (c) 2020-2021 Via Technology Ltd. All Rights Reserved.
//
// 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 cl_sys::{
    CL_CONTEXT_DEVICES, CL_CONTEXT_INTEROP_USER_SYNC, CL_CONTEXT_NUM_DEVICES, CL_CONTEXT_PLATFORM,
    CL_CONTEXT_PROPERTIES, CL_CONTEXT_REFERENCE_COUNT,
};

use super::error_codes::{CL_INVALID_VALUE, CL_SUCCESS};
use super::info_type::InfoType;
use super::types::{
    cl_context, cl_context_info, cl_context_properties, cl_device_id, cl_device_type, cl_int,
    cl_uint,
};
use super::{api_info_size, api_info_value, api_info_vector};
use cl_sys::{
    clCreateContext, clCreateContextFromType, clGetContextInfo, clReleaseContext, clRetainContext,
};

use libc::{c_char, c_void, intptr_t, size_t};
use std::mem;
use std::ptr;

// clSetContextDestructorCallback is CL_VERSION_3_0, not in cl_sys yet
#[cfg_attr(not(target_os = "macos"), link(name = "OpenCL"))]
#[cfg_attr(target_os = "macos", link(name = "OpenCL", kind = "framework"))]
extern "system" {
    pub fn clSetContextDestructorCallback(
        context: cl_context,
        pfn_notify: extern "C" fn(cl_context, *const c_void),
        user_data: *mut c_void,
    ) -> cl_int;
}

/// 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<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<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.
#[inline]
pub fn retain_context(context: cl_context) -> Result<(), cl_int> {
    let status: cl_int = unsafe { 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.
#[inline]
pub fn release_context(context: cl_context) -> Result<(), cl_int> {
    let status: cl_int = unsafe { 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: extern "C" fn(cl_context, *const 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);

        release_context(context).unwrap();
    }
}