Skip to main content

g2d_sys/
lib.rs

1// SPDX-FileCopyrightText: Copyright 2025 Au-Zone Technologies
2// SPDX-License-Identifier: Apache-2.0
3
4#![cfg(target_os = "linux")]
5#![allow(non_upper_case_globals)]
6#![allow(non_camel_case_types)]
7#![allow(non_snake_case)]
8#![allow(clippy::missing_safety_doc)]
9
10include!("./ffi.rs");
11
12use four_char_code::{four_char_code, FourCharCode};
13use nix::ioctl_write_ptr;
14use std::{
15    ffi::{c_char, CStr},
16    fmt::Display,
17    os::{
18        fd::RawFd,
19        raw::{c_ulong, c_void},
20    },
21    ptr::null_mut,
22    rc::Rc,
23};
24
25/// 8 bit grayscale, full range
26// pub const GREY: FourCharCode = four_char_code!("Y800");
27pub const YUYV: FourCharCode = four_char_code!("YUYV");
28pub const RGBA: FourCharCode = four_char_code!("RGBA");
29pub const RGB: FourCharCode = four_char_code!("RGB ");
30pub const NV12: FourCharCode = four_char_code!("NV12");
31
32const G2D_2_3_0: Version = Version::new(6, 4, 11, 1049711);
33
34pub type Result<T, E = Error> = std::result::Result<T, E>;
35
36#[derive(Debug)]
37pub enum Error {
38    IoError(std::io::Error),
39    LibraryError(libloading::Error),
40    InvalidFormat(String),
41}
42
43impl From<std::io::Error> for Error {
44    fn from(err: std::io::Error) -> Self {
45        Error::IoError(err)
46    }
47}
48
49impl From<libloading::Error> for Error {
50    fn from(err: libloading::Error) -> Self {
51        Error::LibraryError(err)
52    }
53}
54
55#[derive(Debug, Copy, Clone)]
56pub struct G2DFormat(g2d_format);
57
58impl G2DFormat {
59    /// Try to create a G2DFormat from a FourCharCode
60    /// Supported formats are RGB, RGBA, YUYV, NV12
61    pub fn try_from(fourcc: FourCharCode) -> Result<Self> {
62        fourcc.try_into()
63    }
64
65    /// Get the underlying g2d_format
66    pub fn format(&self) -> g2d_format {
67        self.0
68    }
69}
70
71impl TryFrom<FourCharCode> for G2DFormat {
72    type Error = Error;
73
74    fn try_from(format: FourCharCode) -> Result<Self, Self::Error> {
75        match format {
76            RGB => Ok(G2DFormat(g2d_format_G2D_RGB888)),
77            RGBA => Ok(G2DFormat(g2d_format_G2D_RGBA8888)),
78            YUYV => Ok(G2DFormat(g2d_format_G2D_YUYV)),
79            NV12 => Ok(G2DFormat(g2d_format_G2D_NV12)),
80            // GREY => Ok(G2DFormat(g2d_format_G2D_NV12)),
81            _ => Err(Error::InvalidFormat(format.to_string())),
82        }
83    }
84}
85
86impl TryFrom<G2DFormat> for FourCharCode {
87    type Error = Error;
88
89    /// Try to convert a G2DFormat to a FourCharCode
90    /// Supported formats are RGB, RGBA, YUYV, NV12
91    fn try_from(format: G2DFormat) -> Result<Self, Self::Error> {
92        match format.0 {
93            g2d_format_G2D_RGB888 => Ok(RGB),
94            g2d_format_G2D_RGBA8888 => Ok(RGBA),
95            g2d_format_G2D_YUYV => Ok(YUYV),
96            g2d_format_G2D_NV12 => Ok(NV12),
97            _ => Err(Error::InvalidFormat(format!(
98                "Unsupported G2D format: {format:?}"
99            ))),
100        }
101    }
102}
103
104#[derive(Debug, Copy, Clone, PartialEq, Eq)]
105pub struct G2DPhysical(c_ulong);
106
107impl G2DPhysical {
108    pub fn new(fd: RawFd) -> Result<Self> {
109        let phys = dma_buf_phys(0);
110        let err = unsafe { ioctl_dma_buf_phys(fd, &phys.0).unwrap_or(1) };
111        if err != 0 {
112            return Err(std::io::Error::last_os_error().into());
113        }
114
115        Ok(G2DPhysical(phys.0))
116    }
117
118    pub fn address(&self) -> c_ulong {
119        self.0
120    }
121}
122
123#[repr(C)]
124#[derive(Debug, Copy, Clone)]
125struct dma_buf_phys(std::ffi::c_ulong);
126
127const DMA_BUF_BASE: u8 = b'b';
128const DMA_BUF_IOCTL_PHYS: u8 = 10;
129ioctl_write_ptr!(
130    ioctl_dma_buf_phys,
131    DMA_BUF_BASE,
132    DMA_BUF_IOCTL_PHYS,
133    std::ffi::c_ulong
134);
135
136impl TryFrom<RawFd> for G2DPhysical {
137    type Error = Error;
138
139    fn try_from(fd: RawFd) -> Result<Self, Self::Error> {
140        G2DPhysical::new(fd)
141    }
142}
143
144impl From<u64> for G2DPhysical {
145    fn from(buf: u64) -> Self {
146        G2DPhysical(buf)
147    }
148}
149
150#[repr(C)]
151#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Default, Copy)]
152/// G2D library version as reported by _G2D_VERSION symbol
153pub struct Version {
154    pub major: i64,
155    pub minor: i64,
156    pub patch: i64,
157    pub num: i64,
158}
159
160impl Display for Version {
161    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
162        write!(
163            f,
164            "{}.{}.{}:{}",
165            self.major, self.minor, self.patch, self.num
166        )
167    }
168}
169
170impl Version {
171    const fn new(major: i64, minor: i64, patch: i64, num: i64) -> Self {
172        Version {
173            major,
174            minor,
175            patch,
176            num,
177        }
178    }
179}
180
181fn guess_version(g2d: &g2d) -> Option<Version> {
182    unsafe {
183        let version = g2d
184            .__library
185            .get::<*const *const c_char>(b"_G2D_VERSION")
186            .map_or(None, |v| Some(*v));
187
188        if let Some(v) = version {
189            // Seems like the char sequence is `\n\0$VERSION$6.4.3:398061:d3dac3f35d$\n\0`
190            // So we need to shift the ptr by two
191            let ptr = (*v).byte_offset(2);
192            let s = CStr::from_ptr(ptr).to_string_lossy().to_string();
193            log::debug!("G2D Version string is {s}");
194            // s = "$VERSION$6.4.3:398061:d3dac3f35d$\n"
195            let mut version = G2D_2_3_0;
196            if let Some(s) = s.strip_prefix("$VERSION$") {
197                let parts: Vec<_> = s.split(':').collect();
198                let v: Vec<_> = parts[0].split('.').collect();
199                version.major = v
200                    .first()
201                    .and_then(|s| s.parse().ok())
202                    .unwrap_or(version.major);
203                version.minor = v
204                    .get(1)
205                    .and_then(|s| s.parse().ok())
206                    .unwrap_or(version.minor);
207                version.patch = v
208                    .get(2)
209                    .and_then(|s| s.parse().ok())
210                    .unwrap_or(version.patch);
211                version.num = parts
212                    .get(1)
213                    .and_then(|s| s.parse().ok())
214                    .unwrap_or(version.num);
215            }
216
217            Some(version)
218        } else {
219            None
220        }
221    }
222}
223
224#[repr(C)]
225#[derive(Debug, Clone, Copy, PartialEq)]
226pub struct G2DSurface {
227    pub format: g2d_format,
228    pub planes: [::std::os::raw::c_ulong; 3usize],
229    pub left: ::std::os::raw::c_int,
230    pub top: ::std::os::raw::c_int,
231    pub right: ::std::os::raw::c_int,
232    pub bottom: ::std::os::raw::c_int,
233    #[doc = "< buffer stride, in Pixels"]
234    pub stride: ::std::os::raw::c_int,
235    #[doc = "< surface width, in Pixels"]
236    pub width: ::std::os::raw::c_int,
237    #[doc = "< surface height, in Pixels"]
238    pub height: ::std::os::raw::c_int,
239    #[doc = "< alpha blending parameters"]
240    pub blendfunc: g2d_blend_func,
241    #[doc = "< value is 0 ~ 255"]
242    pub global_alpha: ::std::os::raw::c_int,
243    pub clrcolor: ::std::os::raw::c_int,
244    pub rot: g2d_rotation,
245}
246
247impl Default for G2DSurface {
248    fn default() -> Self {
249        G2DSurface {
250            format: g2d_format_G2D_RGB888,
251            planes: [0, 0, 0],
252            left: 0,
253            top: 0,
254            right: 0,
255            bottom: 0,
256            stride: 0,
257            width: 0,
258            height: 0,
259            blendfunc: g2d_blend_func_G2D_ZERO,
260            global_alpha: 255,
261            clrcolor: 0,
262            rot: g2d_rotation_G2D_ROTATION_0,
263        }
264    }
265}
266
267#[repr(C)]
268#[derive(Debug, Clone, Copy, PartialEq)]
269pub struct G2DSurfaceLegacy {
270    pub format: g2d_format,
271    pub planes: [::std::os::raw::c_int; 3usize],
272    pub left: ::std::os::raw::c_int,
273    pub top: ::std::os::raw::c_int,
274    pub right: ::std::os::raw::c_int,
275    pub bottom: ::std::os::raw::c_int,
276    #[doc = "< buffer stride, in Pixels"]
277    pub stride: ::std::os::raw::c_int,
278    #[doc = "< surface width, in Pixels"]
279    pub width: ::std::os::raw::c_int,
280    #[doc = "< surface height, in Pixels"]
281    pub height: ::std::os::raw::c_int,
282    #[doc = "< alpha blending parameters"]
283    pub blendfunc: g2d_blend_func,
284    #[doc = "< value is 0 ~ 255"]
285    pub global_alpha: ::std::os::raw::c_int,
286    pub clrcolor: ::std::os::raw::c_int,
287    pub rot: g2d_rotation,
288}
289
290impl Default for G2DSurfaceLegacy {
291    fn default() -> Self {
292        G2DSurfaceLegacy {
293            format: g2d_format_G2D_RGB888,
294            planes: [0, 0, 0],
295            left: 0,
296            top: 0,
297            right: 0,
298            bottom: 0,
299            stride: 0,
300            width: 0,
301            height: 0,
302            blendfunc: g2d_blend_func_G2D_ZERO,
303            global_alpha: 255,
304            clrcolor: 0,
305            rot: g2d_rotation_G2D_ROTATION_0,
306        }
307    }
308}
309
310impl From<&G2DSurface> for G2DSurfaceLegacy {
311    fn from(surface: &G2DSurface) -> Self {
312        G2DSurfaceLegacy {
313            format: surface.format,
314            planes: [
315                surface.planes[0] as ::std::os::raw::c_int,
316                surface.planes[1] as ::std::os::raw::c_int,
317                surface.planes[2] as ::std::os::raw::c_int,
318            ],
319            left: surface.left,
320            top: surface.top,
321            right: surface.right,
322            bottom: surface.bottom,
323            stride: surface.stride,
324            width: surface.width,
325            height: surface.height,
326            blendfunc: surface.blendfunc,
327            global_alpha: surface.global_alpha,
328            clrcolor: surface.clrcolor,
329            rot: surface.rot,
330        }
331    }
332}
333
334#[derive(Debug)]
335pub struct G2D {
336    pub lib: Rc<g2d>,
337    pub handle: *mut c_void,
338    pub version: Version,
339}
340
341impl G2D {
342    pub fn new<P>(path: P) -> Result<Self>
343    where
344        P: AsRef<::std::ffi::OsStr>,
345    {
346        let lib = unsafe { g2d::new(path)? };
347        let mut handle: *mut c_void = null_mut();
348
349        if unsafe { lib.g2d_open(&mut handle) } != 0 {
350            return Err(std::io::Error::last_os_error().into());
351        }
352
353        let version = guess_version(&lib).unwrap_or(G2D_2_3_0);
354
355        Ok(Self {
356            lib: Rc::new(lib),
357            version,
358            handle,
359        })
360    }
361
362    pub fn version(&self) -> Version {
363        self.version
364    }
365
366    pub fn clear(&self, dst: &mut G2DSurface, color: [u8; 4]) -> Result<()> {
367        dst.clrcolor = i32::from_le_bytes(color);
368        let ret = if self.version >= G2D_2_3_0 {
369            unsafe {
370                self.lib
371                    .g2d_clear(self.handle, dst as *const _ as *mut g2d_surface)
372            }
373        } else {
374            let dst: G2DSurfaceLegacy = (dst as &G2DSurface).into();
375            unsafe {
376                self.lib
377                    .g2d_clear(self.handle, &dst as *const _ as *mut g2d_surface)
378            }
379        };
380
381        if ret != 0 {
382            return Err(std::io::Error::last_os_error().into());
383        }
384
385        if unsafe { self.lib.g2d_finish(self.handle) } != 0 {
386            return Err(std::io::Error::last_os_error().into());
387        }
388        dst.clrcolor = 0;
389
390        Ok(())
391    }
392
393    pub fn blit(&self, src: &G2DSurface, dst: &G2DSurface) -> Result<()> {
394        let ret = if self.version >= G2D_2_3_0 {
395            unsafe {
396                self.lib.g2d_blit(
397                    self.handle,
398                    src as *const _ as *mut g2d_surface,
399                    dst as *const _ as *mut g2d_surface,
400                )
401            }
402        } else {
403            let src: G2DSurfaceLegacy = src.into();
404            let dst: G2DSurfaceLegacy = dst.into();
405
406            unsafe {
407                self.lib.g2d_blit(
408                    self.handle,
409                    &src as *const _ as *mut g2d_surface,
410                    &dst as *const _ as *mut g2d_surface,
411                )
412            }
413        };
414
415        if ret != 0 {
416            return Err(std::io::Error::last_os_error().into());
417        }
418
419        if unsafe { self.lib.g2d_finish(self.handle) } != 0 {
420            return Err(std::io::Error::last_os_error().into());
421        }
422
423        Ok(())
424    }
425
426    pub fn set_bt601_colorspace(&mut self) -> Result<()> {
427        if unsafe {
428            self.lib
429                .g2d_enable(self.handle, g2d_cap_mode_G2D_YUV_BT_601)
430        } != 0
431        {
432            return Err(std::io::Error::last_os_error().into());
433        }
434        if unsafe {
435            self.lib
436                .g2d_disable(self.handle, g2d_cap_mode_G2D_YUV_BT_709)
437        } != 0
438        {
439            return Err(std::io::Error::last_os_error().into());
440        }
441        Ok(())
442    }
443
444    pub fn set_bt709_colorspace(&mut self) -> Result<()> {
445        if unsafe {
446            self.lib
447                .g2d_disable(self.handle, g2d_cap_mode_G2D_YUV_BT_601)
448        } != 0
449        {
450            return Err(std::io::Error::last_os_error().into());
451        }
452
453        if unsafe {
454            self.lib
455                .g2d_disable(self.handle, g2d_cap_mode_G2D_YUV_BT_601FR)
456        } != 0
457        {
458            return Err(std::io::Error::last_os_error().into());
459        }
460
461        if unsafe {
462            self.lib
463                .g2d_disable(self.handle, g2d_cap_mode_G2D_YUV_BT_709FR)
464        } != 0
465        {
466            return Err(std::io::Error::last_os_error().into());
467        }
468
469        if unsafe {
470            self.lib
471                .g2d_enable(self.handle, g2d_cap_mode_G2D_YUV_BT_709)
472        } != 0
473        {
474            return Err(std::io::Error::last_os_error().into());
475        }
476        Ok(())
477    }
478}
479
480impl Drop for G2D {
481    fn drop(&mut self) {
482        if !self.handle.is_null() {
483            unsafe {
484                self.lib.g2d_close(self.handle);
485            }
486        }
487    }
488}