Skip to main content

julia_set/
opencl.rs

1use lazy_static::lazy_static;
2use ocl::{
3    builders::BuildOpt, flags, prm::Float2, Buffer, Context, Device, OclPrm, Platform, ProQue,
4    Queue,
5};
6
7use std::sync::Mutex;
8
9use crate::{compiler::Compiler, Backend, Function, ImageBuffer, Params, Render};
10
11const PROGRAM: &str = include_str!(concat!(env!("OUT_DIR"), "/program.cl"));
12
13/// Backend based on [OpenCL].
14///
15/// [OpenCL]: https://www.khronos.org/opencl/
16#[cfg_attr(docsrs, doc(cfg(feature = "opencl_backend")))]
17#[derive(Debug, Clone, Copy, Default)]
18pub struct OpenCl;
19
20impl Backend<&Function> for OpenCl {
21    type Error = ocl::Error;
22    type Program = OpenClProgram;
23
24    fn create_program(&self, function: &Function) -> Result<Self::Program, Self::Error> {
25        let compiled = Compiler::for_ocl().compile(function);
26        OpenClProgram::new(compiled)
27    }
28}
29
30/// Program produced by the [`OpenCl`] backend.
31#[cfg_attr(docsrs, doc(cfg(feature = "opencl_backend")))]
32#[derive(Debug)]
33pub struct OpenClProgram {
34    inner: ProQue,
35}
36
37impl OpenClProgram {
38    fn new(compiled: String) -> ocl::Result<Self> {
39        let mut program_builder = ocl::Program::builder();
40        let define = BuildOpt::IncludeDefine {
41            ident: "COMPUTE(z)".to_owned(),
42            val: compiled,
43        };
44        program_builder.bo(define).source(PROGRAM);
45
46        // For some reason, certain OpenCL implementations (e.g., POCL) do not work well
47        // when the list of devices for a platform is queried from multiple threads.
48        // Hence, we introduce a `Mutex` to serialize these calls.
49        lazy_static! {
50            static ref MUTEX: Mutex<()> = Mutex::new(());
51        }
52        let (platform, device) = {
53            let _lock = MUTEX.lock().ok();
54            let platform = Platform::first()?;
55            (platform, Device::first(platform)?)
56        };
57
58        let context = Context::builder()
59            .platform(platform)
60            .devices(&device)
61            .build()?;
62        let inner = ProQue::new(
63            context.clone(),
64            Queue::new(&context, device, None)?,
65            program_builder.build(&context)?,
66            None::<usize>,
67        );
68        Ok(Self { inner })
69    }
70}
71
72impl Render for OpenClProgram {
73    type Error = ocl::Error;
74
75    fn render(&self, params: &Params) -> Result<ImageBuffer, Self::Error> {
76        let pixels = params.image_size[0]
77            .checked_mul(params.image_size[1])
78            .expect("Overflow in image dimensions");
79        let buffer: Buffer<u8> = Buffer::builder()
80            .queue(self.inner.queue().clone())
81            .len(pixels)
82            .flags(flags::MEM_WRITE_ONLY | flags::MEM_HOST_READ_ONLY)
83            .build()?;
84
85        let cl_params = ClParams {
86            view_center: Float2::new(params.view_center[0], params.view_center[1]),
87            view_size: Float2::new(params.view_width(), params.view_height),
88            inf_distance_sq: params.inf_distance * params.inf_distance,
89            max_iterations: params.max_iterations,
90        };
91        let kernel = self
92            .inner
93            .kernel_builder("julia")
94            .arg_named("output", &buffer)
95            .arg_named("params", cl_params)
96            .build()?;
97
98        let command = kernel.cmd().global_work_size(params.image_size);
99        unsafe { command.enq()? };
100
101        let mut image = ImageBuffer::new(params.image_size[0], params.image_size[1]);
102        buffer.read(&mut *image).enq()?;
103        Ok(image)
104    }
105}
106
107#[derive(Debug, Clone, Copy, Default, PartialEq)]
108#[repr(C, packed)]
109struct ClParams {
110    view_center: Float2,
111    view_size: Float2,
112    inf_distance_sq: f32,
113    max_iterations: u8,
114}
115
116// Safety ensured by the same alignment here and in OCL code.
117unsafe impl OclPrm for ClParams {}