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
204
205
206
207
208
209
210
211
/* SPDX-License-Identifier: LGPL-2.1-or-later

Copyright (c) 2022 Purism, SPC
Copyright (c) 2024 DorotaC
*/
/*! EGL procedures
*/

pub mod headless;

use crate::egl_ext as ext;
use crate::glerr;
use dma_boom::DmaBuf;
use ext::{KhrImageBaseInstance, OesImageInstance};
use gbm::Format;
use gl::types::GLuint;
pub use khronos_egl;
use khronos_egl as egl;
use std::os::fd::{AsRawFd, AsFd, BorrowedFd, OwnedFd};
use std::ptr;
use thiserror::Error;


// The texture number is not arbitrary, so creating/replacing one should not be possible outside of code which knows how to handle this..
/// OpenGL texture identifier, used for binding textures to context.
#[derive(Copy, Clone, Debug)]
pub struct TextureId(GLuint);

impl TextureId {
    /// Returns the OpenGL texture identifier
    pub fn as_gl_id(&self) -> GLuint {
        self.0
    }
}

#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct Dimensions {
    pub width: u32,
    pub height: u32,
}

/// A texture attached to an image
#[derive(Debug)]
pub struct Texture {
    id: TextureId,
    dimensions: Dimensions,
    format: Format,
}

impl Texture {
    pub fn id(&self) ->TextureId {
        self.id
    }
    pub fn dimensions(&self) -> Dimensions {
        self.dimensions
    }
    pub fn format(&self) -> Format {
        self.format
    }
}

/// The image is created from a fd, but it does not get invalidated when the fd is released
#[derive(Debug)]
pub struct DmabufImage {
    texture: TextureId,
    image: egl::Image,
    dimensions: Dimensions,
    format: Format,
}

/// The image is created from a fd, but it does not get invalidated when the fd is released
#[derive(Debug)]
pub struct DmabufTexture {
    texture: glium::Texture2d,
    image: egl::Image,
    dimensions: Dimensions,
    format: Format,
}


// FIXME: is it actually safe? egl::Image is not Send, but glutin requires Send in Context::exec_in_context for some reason. Can't avoid using that to create a DmabufImage.
unsafe impl Send for DmabufImage{}

impl DmabufImage {
    // TODO: don't just assume this is bound. Dmabufimage should contain a bound texture
    pub fn get_texture(&self) ->Texture {
        Texture {
            id: self.texture,
            dimensions: self.dimensions,
            format: self.format,
        }
    }
}

pub type DmabufImageRef = DmabufImage;

/// Holds the file description in existence for as long as the EGL image exists.
///
/// TODO: clear texture?
/// TODO: make a note if this is really needed
#[derive(Debug)]
pub struct OwnedDmabufImage {
    dmafd: DmaBuf,
    image: DmabufImageRef,
}

impl OwnedDmabufImage {
    pub fn get_texture(&self) -> &DmabufImageRef {
        &self.image
    }
    
    pub fn dmabuf(&self) -> &DmaBuf {
        &self.dmafd
    }
    
    pub fn fd(&self) -> BorrowedFd {
        self.dmafd.as_fd()
    }
}

fn format_bpp(fourcc: Format) -> Option<usize> {
    match fourcc {
        Format::Argb8888 => Some(4),
        Format::R8 => Some(1),
        Format::R16 => Some(2),
        Format::Rg88 => Some(2),
        Format::Gr88 => Some(2),
        _ => None,
    }
}


#[derive(Debug, Error)]
pub enum DmabufImportError {
    #[error("This format is TODO")]
    UnsupportedPixelFormat,
    #[error("EGL call failed")]
    EglError(egl::Error),
    #[error("OpenGL call failed")]
    GlError(crate::glerr::Error),
}

/// Requires the context to already be current
pub unsafe fn import_dmabuf_display(
    display: egl::Display,
    fd: &DmaBuf,
    (width, height): (u32, u32),
    fourcc: Format,
) -> Result<DmabufImage, DmabufImportError> {
    let egl_calls = egl::Instance::new(egl::Static);
    
    let bytes_per_pixel = format_bpp(fourcc).ok_or(DmabufImportError::UnsupportedPixelFormat)?;
    let image = unsafe {
        use ext::image_dma_buf_import as ext;

        egl_calls.create_image_khr(
            display,
            egl::Context::from_ptr(egl::NO_CONTEXT),
            ext::LINUX_DMA_BUF_EXT,
            egl::ClientBuffer::from_ptr(ptr::null_mut()), 
            &[
                egl::WIDTH as _, width as _,
                egl::HEIGHT as _, height as _,
                ext::LINUX_DRM_FOURCC_EXT, fourcc as _,
                ext::DMA_BUF_PLANE0_FD_EXT, fd.as_raw_fd() as _,
                ext::DMA_BUF_PLANE0_OFFSET_EXT, 0,
                ext::DMA_BUF_PLANE0_PITCH_EXT, (width as egl::Int) * (bytes_per_pixel as egl::Int),
                egl::NONE,
            ],
        )
    }.map_err(DmabufImportError::EglError)?;
    
    gl::load_with(|s| egl_calls.get_proc_address(s).unwrap() as _);
    
    let texture = {
        let mut texture = 0;
        glerr::check(unsafe {
            gl::GenTextures(1, &mut texture);
            texture
        })
    }.map_err(DmabufImportError::GlError)?;
    
    attach_image_current(DmabufImage {
        texture: TextureId(texture),
        image,
        dimensions: Dimensions { width, height },
        format: fourcc,
    })
}

/// Creates a texture with the image attached.
///
/// An egl context must be current
unsafe fn attach_image_current(buf: DmabufImage) -> Result<DmabufImage, DmabufImportError> {

    let egl_calls = egl::Instance::new(egl::Static);
    gl::load_with(|s| egl_calls.get_proc_address(s).unwrap() as _);
    
    glerr::check(unsafe {
        gl::BindTexture(gl::TEXTURE_2D, buf.texture.0)
    }).map_err(DmabufImportError::GlError)?;

    // TODO: a renderbuffer used here would/could be faster.
	// EGLImageTargetRenderbufferStorageOES
    unsafe {
        egl_calls.image_target_texture_2d_oes(gl::TEXTURE_2D, buf.image)
    }.map_err(DmabufImportError::EglError)?;
    
    Ok(buf)
}