1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
#![allow(dead_code)]
//! # Linux cross-process DMA-BUF-based image ("texture") sharing
//!
//! An [`Image<FD>`] primarily contains [DMA-BUF] (`FD`-typed) file descriptor(s)
//! (within each [`ImagePlane<FD>`], which also tracks its buffer's "2D slice"),
//! and the ["DRM format"] ([`DrmFormat`]) describing the image's texel encoding,
//! all combined into a conveniently (de)serializable form (as long as `FD` is).
//!
//! ---
//!
//! Under EGL, this allows sharing an OpenGL texture across processes, e.g.:
//! * A creates an `EGLImage` from some OpenGL texture (or another resource)
//! * A exports its `EGLImage` using [`EGL_MESA_image_dma_buf_export`]
//! * A passes to B its [DMA-BUF] file descriptor(s) and ["DRM format"] metadata
//! * B imports it as an `EGLImage` using [`EGL_EXT_image_dma_buf_import`]
//! * B exposes its `EGLImage` as an OpenGL texture using [`glEGLImageTargetTexture2DOES`]
//!
//! [DMA-BUF]: https://docs.kernel.org/driver-api/dma-buf.html
//! ["DRM format"]: https://docs.kernel.org/gpu/drm-kms.html#drm-format-handling
//! [`EGL_MESA_image_dma_buf_export`]: https://registry.khronos.org/EGL/extensions/MESA/EGL_MESA_image_dma_buf_export.txt
//! [`EGL_EXT_image_dma_buf_import`]: https://registry.khronos.org/EGL/extensions/EXT/EGL_EXT_image_dma_buf_import.txt
//! [`glEGLImageTargetTexture2DOES`]: https://registry.khronos.org/OpenGL/extensions/OES/OES_EGL_image.txt

use crate::makepad_micro_serde::*;

use std::os::{self, fd::AsRawFd as _};

#[derive(Debug, PartialEq, SerBin, DeBin, SerJson, DeJson)]
pub struct Image<FD>
    // HACK(eddyb) hint `{Ser,De}{Bin,Json}` derivers to add their own bounds.
    where FD: Sized
{
    pub drm_format: DrmFormat,
    // FIXME(eddyb) support 2-4 planes (not needed for RGBA, so most likely only
    // relevant to YUV video decode streams - or certain forms of compression).
    pub planes: ImagePlane<FD>,
}

impl<FD> Image<FD> {
    pub fn planes_map<FD2>(self, f: impl Fn(ImagePlane<FD>) -> ImagePlane<FD2>) -> Image<FD2> {
        let Image { drm_format, planes: plane0 } = self;
        Image { drm_format, planes: f(plane0) }
    }
    pub fn planes_ref_map<FD2>(&self, f: impl Fn(&ImagePlane<FD>) -> ImagePlane<FD2>) -> Image<FD2> {
        let Image { drm_format, planes: ref plane0 } = *self;
        Image { drm_format, planes: f(plane0) }
    }
}

impl<FD> Copy for Image<FD> where ImagePlane<FD>: Copy {}
impl<FD> Clone for Image<FD> where ImagePlane<FD>: Clone {
    fn clone(&self) -> Self {
        self.planes_ref_map(|plane| plane.clone())
    }
}

impl Image<os::fd::OwnedFd> {
    pub fn as_remote(&self) -> Image<RemoteFd> {
        self.planes_ref_map(|plane| plane.as_remote())
    }
}

/// In the Linux DRM+KMS system (i.e. kernel-side GPU drivers), a "DRM format"
/// is an image format (i.e. a specific byte-level encoding of texel data)
/// that framebuffers (or more generally "surfaces" / "images") could use,
/// provided that all the GPUs involved support the specific format used.
///
/// See also <https://docs.kernel.org/gpu/drm-kms.html#drm-format-handling>.
#[derive(Copy, Clone, Debug, PartialEq, SerBin, DeBin, SerJson, DeJson)]
pub struct DrmFormat {
    /// FourCC code for a "DRM format", i.e. one of the `DRM_FORMAT_*` values
    /// defined in `drm/drm_fourcc.h`, and the main aspect of a "DRM format"
    /// that userspace needs to care about (e.g. RGB vs YUV, bit width, etc.).
    ///
    /// For example, non-HDR RGBA surfaces will almost always use the format
    /// `DRM_FORMAT_ABGR8888` (with FourCC `"AB24"`, i.e. `0x34324241`), and:
    /// - "A" can be replaced with "X" (disabling the alpha channel)
    /// - "AB" can be reversed, to get "BA" (ABGR -> BGRA)
    /// - "B" can be replaced with "R" (ABGR -> ARGB)
    /// - "AR" can be reversed, to get "RA" (ARGB -> RGBA)
    /// - "24" can be replaced with "30" or "48" (increasing bits per channel)
    ///
    /// Some formats also require multiple "planes" (i.e. independent buffers),
    /// and while that's commonly for YUV formats, planar RGBA also exists.
    pub fourcc: u32,

    /// Each "DRM format" may be further "modified" with additional features,
    /// describing how memory is accessed by GPU texture units (e.g. "tiling"),
    /// and optionally requiring additional "planes" for compression purposes.
    ///
    /// To userspace, the modifiers are almost always opaque and merely need to
    /// be passed from an image exporter to an image importer, to correctly
    /// interpret the GPU memory in the same way on both sides.
    pub modifiers: u64,
}

#[derive(Debug, PartialEq, SerBin, DeBin, SerJson, DeJson)]
pub struct ImagePlane<FD>
    // HACK(eddyb) hint `{Ser,De}{Bin,Json}` derivers to add their own bounds.
    where FD: Sized
{
    /// Linux DMA-BUF file descriptor, representing a generic GPU buffer object.
    ///
    /// See also <https://docs.kernel.org/driver-api/dma-buf.html>.
    pub dma_buf_fd: FD,

    /// This plane's starting position (in bytes), in the DMA-BUF buffer.
    pub offset: u32,

    /// This plane's stride (aka "pitch") for texel rows, in the DMA-BUF buffer.
    pub stride: u32,
}

impl<FD> ImagePlane<FD> {
    pub fn fd_as_ref(&self) -> ImagePlane<&FD> {
        let ImagePlane { ref dma_buf_fd, offset, stride } = *self;
        ImagePlane { dma_buf_fd, offset, stride }
    }
    pub fn fd_map<FD2>(self, f: impl FnOnce(FD) -> FD2) -> ImagePlane<FD2> {
        let ImagePlane { dma_buf_fd, offset, stride } = self;
        ImagePlane { dma_buf_fd: f(dma_buf_fd), offset, stride }
    }
}

impl Copy for ImagePlane<RemoteFd> {}
impl Clone for ImagePlane<RemoteFd> {
    fn clone(&self) -> Self {
        self.fd_as_ref().fd_map(|&fd| fd)
    }
}

impl Clone for ImagePlane<os::fd::OwnedFd> {
    fn clone(&self) -> Self {
        self.fd_as_ref().fd_map(|fd| fd.try_clone().unwrap())
    }
}

impl ImagePlane<os::fd::OwnedFd> {
    pub fn as_remote(&self) -> ImagePlane<RemoteFd> {
        self.fd_as_ref().fd_map(|fd| RemoteFd {
            remote_pid: std::process::id(),
            remote_fd: fd.as_raw_fd(),
        })
    }
}

// HACK(eddyb) to avoid needing an UNIX domain socket, we pass file descriptors
// to child processes via `pidfd_getfd` (see also `linux_x11_stdin::pid_fd`).
#[derive(Copy, Clone, Debug, PartialEq, SerBin, DeBin, SerJson, DeJson)]
pub struct RemoteFd {
    pub remote_pid: u32,
    pub remote_fd: i32,
}

/// Linux pidfd (file-descriptor-based API for "process handles") wrapper.
///
/// `PidFd` is used here specifically for its ability to clone any file descriptors
/// from a remote process, similar to opening `/proc/$REMOTE_PID/fd/$REMOTE_FD`,
/// but without all the caveats and failure modes around special file descriptors.
//
// FIXME(eddyb) `std::os::linux::process::PidFd` should be used/wrapped instead,
// but for now it's still unstable, and it also lacks `pidfd_getfd` functionality,
// as its main purpose appears to be creating a pidfd from `std::process::Command`.
#[allow(non_camel_case_types, non_upper_case_globals)]
pub(super) mod pid_fd {
    use std::{
        ffi::{c_int, c_long, c_uint},
        io,
        os::{self, fd::{AsRawFd as _, FromRawFd as _}},
    };

    pub(crate) type pid_t = c_int;

    extern "C" { fn syscall(num: c_long, ...) -> c_long; }
    const SYS_pidfd_open: c_long = 434;
    const SYS_pidfd_getfd: c_long = 438;

    pub(crate) struct PidFd(os::fd::OwnedFd);
    impl PidFd {
        pub fn from_remote_pid(remote_pid: pid_t) -> Result<PidFd, io::Error> {
            unsafe {
                let flags: c_uint = 0;
                let pid_fd = syscall(SYS_pidfd_open, remote_pid, flags);
                if pid_fd == -1 {
                    Err(io::Error::last_os_error())
                } else {
                    Ok(PidFd(os::fd::OwnedFd::from_raw_fd(pid_fd as os::fd::RawFd)))
                }
            }
        }
        pub fn clone_remote_fd(&self, remote_fd: os::fd::RawFd) -> Result<os::fd::OwnedFd, io::Error> {
            unsafe {
                let flags: c_uint = 0;
                let cloned_fd = syscall(SYS_pidfd_getfd, self.0.as_raw_fd(), remote_fd, flags);
                if cloned_fd == -1 {
                    Err(io::Error::last_os_error())
                } else {
                    Ok(os::fd::OwnedFd::from_raw_fd(cloned_fd as os::fd::RawFd))
                }
            }
        }
    }
}