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