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