opencl3 0.1.2

A Rust implementation of the Khronos OpenCL 3.0 API.
Documentation
// 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.

pub use cl3::context::{CL_CONTEXT_INTEROP_USER_SYNC, CL_CONTEXT_PLATFORM};

use super::command_queue::CommandQueue;
use super::device::{Device, SubDevice};
use super::kernel::Kernel;
use super::memory::get_supported_image_formats;
use super::program::Program;

use cl3::context;
use cl3::types::{
    cl_command_queue_properties, cl_context, cl_context_properties, cl_device_id,
    cl_device_partition_property, cl_device_svm_capabilities, cl_image_format, cl_int,
    cl_mem_flags, cl_mem_object_type, cl_uint,
};
use libc::{c_char, c_void, intptr_t, size_t};
use std::collections::HashMap;
use std::ffi::{CStr, CString};
use std::ptr;

/// An OpenCL context.  
/// A Context manages OpenCL objects that are constructed from it, i.e.:
/// * [CommandQueue]s
/// * [Program]s
/// * [Kernel]s
/// * [SubDevice]s
///
/// It implements the Drop trait which clears the collections of OpenCL objects
/// so that they are released when the Context goes out of scope.
pub struct Context {
    context: cl_context,
    devices: Vec<cl_device_id>,
    sub_devices: Vec<SubDevice>,
    queues: Vec<CommandQueue>,
    programs: Vec<Program>,
    kernels: HashMap<CString, Kernel>,
}

impl Drop for Context {
    fn drop(&mut self) {
        self.kernels.clear();
        self.programs.clear();
        self.queues.clear();
        self.sub_devices.clear();
        self.devices.clear();
        context::release_context(self.context).unwrap();
    }
}

impl Context {
    fn new(context: cl_context, devices: Vec<cl_device_id>) -> Context {
        Context {
            context,
            devices,
            sub_devices: Vec::<SubDevice>::default(),
            queues: Vec::<CommandQueue>::default(),
            programs: Vec::<Program>::default(),
            kernels: HashMap::<CString, Kernel>::default(),
        }
    }

    /// Get the underlying OpenCL cl_context.
    pub fn get(&self) -> cl_context {
        self.context
    }

    /// Create a Context from a vector of cl_device_ids.  
    /// Note: the vector of cl_device_ids are moved into Context
    ///
    /// * `devices` - a vector of cl_device_ids 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.
    pub fn from_devices(
        devices: Vec<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<Context, cl_int> {
        let context = context::create_context(&devices, properties, pfn_notify, user_data)?;
        Ok(Context::new(context, devices))
    }

    /// Create a Context from a [Device].
    ///
    /// * `device` - a [Device].
    ///
    /// returns a Result containing the new OpenCL context
    /// or the error code from the OpenCL C API function.
    pub fn from_device(device: Device) -> Result<Context, cl_int> {
        let devices: Vec<cl_device_id> = vec![device.id()];
        Context::from_devices(devices, ptr::null(), None, ptr::null_mut())
    }

    /// Add a [CommandQueue] to the Context for it to manage.
    ///
    /// * `queue` - a command queue on one of the devices or sub-devices
    /// managed by the Context.
    pub fn add_command_queue(&mut self, queue: CommandQueue) {
        self.queues.push(queue);
    }

    /// Create a [CommandQueue] for every device and append them to the queues
    /// managed by this context.  
    /// Deprecated in CL_VERSION_2_0 by create_command_queue_with_properties.
    ///
    /// * `properties` - a list of properties for the command-queue, see
    /// [cl_command_queue_properties](https://www.khronos.org/registry/OpenCL/specs/3.0-unified/html/OpenCL_API.html#legacy-queue-properties-table).
    ///
    /// returns an empty Result
    /// or the error code from the OpenCL C API function.
    #[cfg(feature = "CL_VERSION_1_2")]
    pub fn create_command_queues(
        &mut self,
        properties: cl_command_queue_properties,
    ) -> Result<(), cl_int> {
        for index in 0..self.devices.len() {
            let device = self.devices[index];
            let queue = CommandQueue::create(self.context, device, properties)?;
            self.add_command_queue(queue);
        }
        Ok(())
    }

    /// Create a [CommandQueue] for every device and append them to the queues
    /// managed by this context.  
    /// CL_VERSION_2_0 onwards.
    ///
    /// * `properties` - a null terminated list of properties for the command-queue, see
    /// [cl_queue_properties](https://www.khronos.org/registry/OpenCL/specs/3.0-unified/html/OpenCL_API.html#queue-properties-table).
    ///
    /// returns an empty Result
    /// or the error code from the OpenCL C API function.
    #[cfg(feature = "CL_VERSION_2_0")]
    pub fn create_command_queues_with_properties(
        &mut self,
        properties: cl_command_queue_properties,
        queue_size: cl_uint,
    ) -> Result<(), cl_int> {
        for index in 0..self.devices.len() {
            let device = self.devices[index];
            let queue =
                CommandQueue::create_with_properties(self.context, device, properties, queue_size)?;
            self.add_command_queue(queue);
        }
        Ok(())
    }

    /// Add a built [Program] to the Context for it to manage.  
    /// It also creates and manages the [Kernel]s in the program.
    ///
    /// * `program` - a [Program] to be managed by the Context.
    ///
    /// returns a Result containing the number of kernels in the Program.
    /// or the error code from the OpenCL C API function.
    pub fn add_program(&mut self, program: Program) -> Result<usize, cl_int> {
        let kernels = program.create_kernels_in_program()?;
        let count = kernels.len();
        for kernel in kernels {
            let kernel = Kernel::new(kernel)?;
            let name = kernel.function_name()?;
            self.kernels.insert(name, kernel);
        }
        self.programs.push(program);
        Ok(count)
    }

    /// Create and build a Program from source code.  
    /// It also creates and manages the [Kernel]s in the program.
    ///
    /// * `src` - a CStr containing the source code character string.
    /// * `options` - the build options in a null-terminated string.
    ///
    /// returns a Result containing the number of kernels in the Program.
    /// or the error code from the OpenCL C API function.
    pub fn build_program_from_source(
        &mut self,
        src: &CStr,
        options: &CStr,
    ) -> Result<usize, cl_int> {
        let src_array = [src];
        let program = Program::create_from_source(self.context, &src_array)?;
        program.build(&self.devices, &options)?;
        self.add_program(program)
    }

    /// Create and build a Program from binaries.  
    /// It also creates and manages the [Kernel]s in the program.
    ///
    /// * `src` - a CStr containing the source code character string.
    /// * `binaries` - a slice of program binaries slices.
    /// * `options` - the build options in a null-terminated string.
    ///
    /// returns a Result containing the number of kernels in the Program.
    /// or the error code from the OpenCL C API function.
    pub fn build_program_from_binary(
        &mut self,
        binaries: &[&[u8]],
        options: &CStr,
    ) -> Result<usize, cl_int> {
        let program = Program::create_from_binary(self.context, &self.devices, binaries)?;
        program.build(&self.devices, &options)?;
        self.add_program(program)
    }

    /// Create sub-devices from a device.
    /// Stores the sub-devices in the sub_devices vector.
    ///
    /// * `device` - the cl_device_id.
    /// * `properties` - the slice of cl_device_partition_property, see
    /// [Subdevice Partition](https://www.khronos.org/registry/OpenCL/specs/3.0-unified/html/OpenCL_API.html#subdevice-partition-table).
    ///
    /// returns a Result containing the number of sub-devices created
    /// or the error code from the OpenCL C API function.
    pub fn create_sub_devices(
        &mut self,
        device: cl_device_id,
        properties: &[cl_device_partition_property],
    ) -> Result<usize, cl_int> {
        let device = Device::new(device);
        let sub_devs = device.create_sub_devices(properties)?;
        let count = sub_devs.len();
        for device_id in sub_devs {
            self.sub_devices.push(SubDevice::new(device_id));
        }
        Ok(count)
    }

    /// Get the kernel with the given name.
    ///
    /// * `kernel_name` -  the name of the [Kernel]
    ///
    /// returns an Option containing a reference to the [Kernel] on None.
    pub fn get_kernel(&self, kernel_name: &CStr) -> Option<&Kernel> {
        self.kernels.get::<CStr>(&kernel_name)
    }

    /// Get the common Shared Virtual Memory (SVM) capabilities of the
    /// devices in the Context.
    pub fn get_svm_mem_capability(&self) -> cl_device_svm_capabilities {
        let device = Device::new(self.devices[0]);
        let mut svm_capability = device.svm_mem_capability();

        for index in 1..self.devices.len() {
            let device = Device::new(self.devices[index]);
            svm_capability &= device.svm_mem_capability();
        }

        svm_capability
    }

    /// Get the list of image formats supported by the Context for an image type,
    /// and allocation information.  
    /// Calls clGetSupportedImageFormats to get the desired information about the program.
    ///
    /// * `flags` - a bit-field used to specify allocation and usage information
    /// about the image memory object being created, see:
    /// [Memory Flags](https://www.khronos.org/registry/OpenCL/specs/3.0-unified/html/OpenCL_API.html#memory-flags-table).
    /// * `image_type` - describes the image type.
    ///
    /// returns a Result containing the desired information in an InfoType enum
    /// or the error code from the OpenCL C API function.
    pub fn get_supported_image_formats(
        &self,
        flags: cl_mem_flags,
        image_type: cl_mem_object_type,
    ) -> Result<Vec<cl_image_format>, cl_int> {
        get_supported_image_formats(self.context, flags, image_type)
    }

    /// Replace the default command queue on an OpenCL device.  
    /// CL_VERSION_2_1
    ///
    /// * `device` - a valid OpenCL device associated with context.
    /// * `command_queue` - a command queue object which replaces the default
    /// device command queue.
    ///
    /// returns an empty Result or the error code from the OpenCL C API function.
    #[cfg(feature = "CL_VERSION_2_1")]
    #[inline]
    pub fn set_default_device_command_queue(
        &self,
        device: cl_device_id,
        queue: &CommandQueue,
    ) -> Result<(), cl_int> {
        set_default_device_command_queue(self.context, device, queue.get())
    }

    pub fn devices(&self) -> &[cl_device_id] {
        &self.devices
    }

    pub fn sub_devices(&self) -> &[SubDevice] {
        &self.sub_devices
    }

    pub fn queues(&self) -> &[CommandQueue] {
        &self.queues
    }

    pub fn default_queue(&self) -> &CommandQueue {
        &self.queues[0]
    }

    pub fn programs(&self) -> &[Program] {
        &self.programs
    }

    pub fn kernels(&self) -> &HashMap<CString, Kernel> {
        &self.kernels
    }

    #[cfg(feature = "CL_VERSION_3_0")]
    #[inline]
    pub fn set_destructor_callback(
        &self,
        pfn_notify: extern "C" fn(cl_context, *const c_void),
        user_data: *mut c_void,
    ) -> Result<(), cl_int> {
        set_context_destructor_callback(self.context, pfn_notify, user_data)
    }

    pub fn reference_count(&self) -> Result<cl_uint, cl_int> {
        Ok(context::get_context_info(
            self.context,
            context::ContextInfo::CL_CONTEXT_REFERENCE_COUNT,
        )?
        .to_uint())
    }

    pub fn properties(&self) -> Result<Vec<intptr_t>, cl_int> {
        Ok(
            context::get_context_info(self.context, context::ContextInfo::CL_CONTEXT_PROPERTIES)?
                .to_vec_intptr(),
        )
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::device::Device;
    use crate::platform::get_platforms;
    use cl3::device::CL_DEVICE_TYPE_GPU;

    #[test]
    fn test_context() {
        let platforms = get_platforms().unwrap();
        assert!(0 < platforms.len());

        // Get the first platform
        let platform = &platforms[0];

        let devices = platform.get_devices(CL_DEVICE_TYPE_GPU).unwrap();
        assert!(0 < devices.len());

        // Get the first device
        let device = Device::new(devices[0]);
        let context = Context::from_device(device).unwrap();

        println!(
            "CL_CONTEXT_REFERENCE_COUNT: {}",
            context.reference_count().unwrap()
        );
    }
}