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
212
213
214
215
216
217
218
219
220
221
222
223
224
 /* SPDX-License-Identifier: LGPL-2.1-or-later

Copyright (c) 2022 Purism, SPC
Copyright (c) 2024 DorotaC
*/
 
/*! Headless (GBM) EGL context.
*/

use crate::egl_ext as ext;
use dma_boom::DmaBuf;
use ext::ExtPlatformBaseInstance;
use gbm::AsRaw;
use khronos_egl as egl;
use std::ffi::c_void;
use std::fs;
use std::fs::File;
use std::ops::Deref;
use std::ptr;
use std::sync::Arc;
use std::sync::atomic;
use std::sync::atomic::AtomicUsize;
use super::DmabufImportError;
use super::OwnedDmabufImage;


// TODO: should this be Clone? Is concurrent mutable access to raw display and context bad?
// Currently, Backend needs a copy, while a copy remains in Facade.
/// GBM EGL GPU context
#[derive(Clone, Debug)]
pub struct Egl {
    display: egl::Display,
    context: egl::Context,
    config: egl::Config,
    device: Arc<gbm::Device<File>>,
}

impl Egl {
    unsafe fn make_current(&self) {
        egl::Instance::new(egl::Static)
            .make_current(
                self.display,
                None,
                None,
                Some(self.context),
            )
            .unwrap()
    }
    unsafe fn release_current(&self) {
        egl::Instance::new(egl::Static)
            .make_current(
                self.display,
                None,
                None,
                None,
            )
            .unwrap()
    }
}


/// A shared reference to a GBM EGL GPU context.
#[derive(Clone)]
pub struct ContextRef {
    pub egl: Egl,
    /// Number of times make_current was called.
    // This counts values across threads, even though 2 make_current calls betwen threads are trouble.
    // It's because ContextRef itself will move between threads on the C side, and the data structures must survive that.
    // It's easier to be synced across threads than to reflect the restriction.
    current: Arc<AtomicUsize>,
}

impl ContextRef {
    pub fn new() -> Self {
        let egl_calls = egl::Instance::new(egl::Static);

        let gbm = gbm::Device::new(
            // WARNING: Rust is said to always use CLOEXEC, so it's not specified here. It's still needed!
            // https://github.com/rust-lang/rust/pull/27971
            fs::OpenOptions::new().read(true).write(true).open("/dev/dri/renderD128").unwrap()
        ).unwrap();

        egl_calls.bind_api(egl::OPENGL_API).unwrap();

        // Older API versions don't have get_platform_display (TODO: check, this is from memory of Librem 5), so use "_ext" despite the reimplementation.
        let display: egl::Display = unsafe { egl_calls.get_platform_display_ext(
            ext::mesa_platform_gbm::PLATFORM_GBM_MESA,
            gbm.as_raw() as _,
            &[egl::ATTRIB_NONE],
        )}.unwrap();

        egl_calls.initialize(display).unwrap();

        // Set context
        
        // TODO: try EGL_KHR_no_config_context
        
        // get an appropriate EGL frame buffer configuration
        let configs = {
            let mut configs = Vec::with_capacity(32);
            egl_calls.choose_config(
                display, 
                &[
                    egl::BUFFER_SIZE, 32,
                    egl::DEPTH_SIZE, egl::DONT_CARE,
                    egl::STENCIL_SIZE, egl::DONT_CARE,
                    egl::RENDERABLE_TYPE, egl::OPENGL_ES2_BIT,
                    egl::SURFACE_TYPE, egl::WINDOW_BIT,
                    egl::NONE,
                ],
                &mut configs,
            ).unwrap();
            configs
        };
        
        // Find a config whose native visual ID is the desired GBM format.
        let config = configs.into_iter().find(|config|
            egl_calls.get_config_attrib(
                display,
                *config,
                egl::NATIVE_VISUAL_ID
            ).unwrap() == gbm::Format::Argb8888 as i32
        ).unwrap();
        
        let context = egl_calls
            .create_context(
                display,
                config,
                None,
                &[
                    egl::CONTEXT_MAJOR_VERSION, 1,
                    ext::CONTEXT_FLAGS_KHR, ext::CONTEXT_OPENGL_DEBUG_BIT_KHR,
                    egl::NONE,
                 ],
             ).unwrap();
        
        Self {
            egl: Egl { context, config, device: Arc::new(gbm), display },
            current: Arc::new(AtomicUsize::new(0)),
        }
    }
    
    /// Only needed for raw::backend.
    /// Glium doesn't know that CurrentContext needs to be held in scope.
    /// Unsafe because it requires a manual release.
    pub unsafe fn force_make_current(&self) {
        if self.current.load(atomic::Ordering::SeqCst) == 0 {
            self.egl.make_current();
        }
    }

    fn release_current(&self) {
        if self.current.fetch_sub(1, atomic::Ordering::SeqCst) == 0 {
            unsafe { self.egl.release_current() };
        }
    }

    pub fn get_proc_address(procname: &str) -> *const c_void {
        egl::Instance::new(egl::Static)
            .get_proc_address(procname)
            .map(|a| a as *const c_void)
            .unwrap_or(ptr::null())
    }
    
    pub fn get_device(&self) -> Arc<gbm::Device<File>>{
        self.egl.device.clone()
    }
}

impl EglContext for ContextRef {
    fn get_display(&self) -> egl::Display {
        self.egl.display
    }

    /// Acquires the context.
    /// Relase by letting CurrentContext go out of scope.
    /// CAUTION: this will crash if multiple contexts are interleaved in a single thread. (TODO? thread-local storage could help.)
    fn make_current<'a>(&'a self) -> CurrentContext<'a> {
        if self.current.fetch_add(1, atomic::Ordering::SeqCst) == 1 {
            unsafe { self.egl.make_current() };
        }
        CurrentContext(&self)
    }
}

/// Automatically releases the context by going out of scope.
pub struct CurrentContext<'a>(&'a ContextRef);

impl<'a> Deref for CurrentContext<'a> {
    type Target = ContextRef;
    fn deref(&self) -> &Self::Target {
        self.0
    }
}

impl<'a> Drop for CurrentContext<'a> {
    fn drop(&mut self) {
        self.0.release_current();
    }
}


/// WIP: this is to find out what import_dmabuf actually needs
pub trait EglContext {
    fn make_current<'a>(&'a self) -> CurrentContext<'a>;
    fn get_display(&self) -> egl::Display;
}

/// If you intend to import a surface as a rendering target, better use ARGB8888.
/// Otherwise the config won't match.
pub fn import_dmabuf(
    egl: &impl EglContext,
    fd: DmaBuf,
    dimensions: (u32, u32),
    fourcc: gbm::Format,
) -> Result<OwnedDmabufImage, DmabufImportError> {
    let _current = egl.make_current();
    Ok(OwnedDmabufImage {
        image: unsafe {
            super::import_dmabuf_display(egl.get_display(), &fd, dimensions, fourcc)
        }?,
       dmafd: fd,
    })
}