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