1use std::ffi::{c_char, c_void, CStr, CString};
12use std::path::{Path, PathBuf};
13
14use crate::gles::Gles;
15
16type EglDisplay = *mut c_void;
17type EglConfig = *mut c_void;
18type EglContext = *mut c_void;
19type EglSurface = *mut c_void;
20type Attrib = isize;
21
22const EGL_NONE: i32 = 0x3038;
23const EGL_TRUE: u32 = 1;
24const EGL_ALPHA_SIZE: i32 = 0x3021;
25const EGL_BLUE_SIZE: i32 = 0x3022;
26const EGL_GREEN_SIZE: i32 = 0x3023;
27const EGL_RED_SIZE: i32 = 0x3024;
28const EGL_DEPTH_SIZE: i32 = 0x3025;
29const EGL_STENCIL_SIZE: i32 = 0x3026;
30const EGL_SAMPLES: i32 = 0x3031;
31const EGL_SURFACE_TYPE: i32 = 0x3033;
32const EGL_RENDERABLE_TYPE: i32 = 0x3040;
33const EGL_PBUFFER_BIT: i32 = 0x0001;
34const EGL_WINDOW_BIT: i32 = 0x0004;
35const EGL_OPENGL_ES3_BIT: i32 = 0x0040;
36const EGL_HEIGHT: i32 = 0x3056;
37const EGL_WIDTH: i32 = 0x3057;
38const EGL_EXTENSIONS: i32 = 0x3055;
39const EGL_VERSION: i32 = 0x3054;
40const EGL_CONTEXT_MAJOR_VERSION: i32 = 0x3098;
41const EGL_CONTEXT_MINOR_VERSION: i32 = 0x30FB;
42const EGL_CONTEXT_OPENGL_ROBUST_ACCESS_EXT: i32 = 0x30BF;
43const EGL_CONTEXT_WEBGL_COMPATIBILITY_ANGLE: i32 = 0x33AC;
44const EGL_ROBUST_RESOURCE_INITIALIZATION_ANGLE: i32 = 0x3453;
45const EGL_PLATFORM_ANGLE_ANGLE: u32 = 0x3202;
46const EGL_PLATFORM_ANGLE_TYPE_ANGLE: Attrib = 0x3203;
47const EGL_PLATFORM_ANGLE_TYPE_D3D11_ANGLE: Attrib = 0x3208;
48const EGL_PLATFORM_ANGLE_TYPE_VULKAN_ANGLE: Attrib = 0x3450;
49const EGL_PLATFORM_ANGLE_TYPE_METAL_ANGLE: Attrib = 0x3489;
50const EGL_PLATFORM_ANGLE_DEVICE_TYPE_ANGLE: Attrib = 0x3209;
51const EGL_PLATFORM_ANGLE_DEVICE_TYPE_SWIFTSHADER_ANGLE: Attrib = 0x3487;
52const EGL_PLATFORM_ANGLE_NATIVE_PLATFORM_TYPE_ANGLE: Attrib = 0x348F;
53const EGL_PLATFORM_X11_EXT: Attrib = 0x31D5;
54const EGL_PLATFORM_WAYLAND_EXT: Attrib = 0x31D8;
55const EGL_PLATFORM_SURFACELESS_MESA: Attrib = 0x31DD;
56
57#[allow(non_snake_case)]
59struct Egl {
60 GetProcAddress: unsafe extern "system" fn(*const c_char) -> *const c_void,
61 GetPlatformDisplay: unsafe extern "system" fn(u32, *mut c_void, *const Attrib) -> EglDisplay,
62 Initialize: unsafe extern "system" fn(EglDisplay, *mut i32, *mut i32) -> u32,
63 Terminate: unsafe extern "system" fn(EglDisplay) -> u32,
64 QueryString: unsafe extern "system" fn(EglDisplay, i32) -> *const c_char,
65 ChooseConfig: unsafe extern "system" fn(EglDisplay, *const i32, *mut EglConfig, i32, *mut i32) -> u32,
66 CreateContext: unsafe extern "system" fn(EglDisplay, EglConfig, EglContext, *const i32) -> EglContext,
67 DestroyContext: unsafe extern "system" fn(EglDisplay, EglContext) -> u32,
68 CreatePbufferSurface: unsafe extern "system" fn(EglDisplay, EglConfig, *const i32) -> EglSurface,
69 CreateWindowSurface: unsafe extern "system" fn(EglDisplay, EglConfig, *mut c_void, *const i32) -> EglSurface,
70 DestroySurface: unsafe extern "system" fn(EglDisplay, EglSurface) -> u32,
71 QuerySurface: unsafe extern "system" fn(EglDisplay, EglSurface, i32, *mut i32) -> u32,
72 MakeCurrent: unsafe extern "system" fn(EglDisplay, EglSurface, EglSurface, EglContext) -> u32,
73 SwapBuffers: unsafe extern "system" fn(EglDisplay, EglSurface) -> u32,
74 SwapInterval: unsafe extern "system" fn(EglDisplay, i32) -> u32,
75 GetError: unsafe extern "system" fn() -> i32,
76}
77
78#[derive(Clone, Copy)]
80pub enum NativeWindow {
81 AppKit(*mut c_void),
83 Win32(*mut c_void),
85 Xlib(*mut c_void, std::ffi::c_ulong),
87 Wayland(*mut c_void),
89}
90
91pub struct Angle {
93 _lib: libloading::Library,
94 egl: Egl,
95 display: EglDisplay,
96 context: EglContext,
97 surface: EglSurface,
98 pub gl: Gles,
99 pub renderer: String,
100}
101
102unsafe impl Send for Angle {}
104
105const LIB_EGL: &str = if cfg!(target_os = "macos") {
106 "libEGL.dylib"
107} else if cfg!(windows) {
108 "libEGL.dll"
109} else {
110 "libEGL.so"
111};
112
113const PLATFORM: &str = if cfg!(all(target_os = "macos", target_arch = "aarch64")) {
114 "macos-arm64"
115} else if cfg!(target_os = "macos") {
116 "macos-x86_64"
117} else if cfg!(windows) {
118 "windows-x86_64"
119} else if cfg!(target_arch = "aarch64") {
120 "linux-arm64"
121} else {
122 "linux-x86_64"
123};
124
125pub fn find(dir: Option<&Path>) -> Result<PathBuf, String> {
128 let mut tried = Vec::new();
129 let mut candidates: Vec<PathBuf> = Vec::new();
130 if let Some(d) = dir {
131 candidates.push(d.to_path_buf());
132 }
133 if let Some(d) = std::env::var_os("GASM_ANGLE_DIR") {
134 candidates.push(PathBuf::from(d));
135 }
136 if let Some(exe) = std::env::current_exe().ok().and_then(|e| e.parent().map(Path::to_path_buf)) {
137 candidates.push(exe.join("../Frameworks"));
138 candidates.push(exe.clone());
139 candidates.push(exe.join("angle"));
140 }
141 candidates.push(Path::new(env!("CARGO_MANIFEST_DIR")).join("../../tools/angle").join(PLATFORM));
142 for c in candidates {
143 if c.join(LIB_EGL).is_file() {
144 return Ok(c);
145 }
146 tried.push(c.display().to_string());
147 }
148 Err(format!(
149 "ANGLE ({LIB_EGL}) not found; looked in {}. Run scripts/fetch-angle.sh, or pass --gl-lib DIR",
150 tried.join(", ")
151 ))
152}
153
154impl Angle {
155 pub fn offscreen(dir: &Path, w: u32, h: u32, software: bool) -> Result<Angle, String> {
157 choose(software, |backend| {
158 Self::open(dir, None, software, backend, |egl, display, config| unsafe {
159 let attrs = [EGL_WIDTH, w as i32, EGL_HEIGHT, h as i32, EGL_NONE];
160 (egl.CreatePbufferSurface)(display, config, attrs.as_ptr())
161 })
162 })
163 }
164
165 pub fn window(dir: &Path, window: NativeWindow, software: bool) -> Result<Angle, String> {
167 let native = match window {
168 NativeWindow::AppKit(view) => layer_of(view),
169 NativeWindow::Win32(hwnd) => hwnd,
170 NativeWindow::Xlib(_, id) => id as usize as *mut c_void,
171 NativeWindow::Wayland(_) => return Err("gasm:gl: Wayland windows aren't supported yet; run with WAYLAND_DISPLAY= (X11)".into()),
172 };
173 choose(software, |backend| {
174 Self::open(dir, Some(window), software, backend, |egl, display, config| unsafe {
175 (egl.CreateWindowSurface)(display, config, native, [EGL_NONE].as_ptr())
176 })
177 })
178 }
179
180 fn open(
181 dir: &Path,
182 window: Option<NativeWindow>,
183 software: bool,
184 backend: Attrib,
185 surface: impl FnOnce(&Egl, EglDisplay, EglConfig) -> EglSurface,
186 ) -> Result<Angle, String> {
187 if software {
188 let icd = dir.join("vk_swiftshader_icd.json");
191 unsafe {
192 std::env::set_var("VK_ICD_FILENAMES", &icd);
193 std::env::set_var("VK_DRIVER_FILES", &icd);
194 }
195 }
196 let path = dir.join(LIB_EGL);
197 let lib = unsafe { libloading::Library::new(&path) }.map_err(|e| format!("{}: {e}", path.display()))?;
198 let egl = unsafe { load_egl(&lib) }?;
199 unsafe {
200 let mut attrs: Vec<Attrib> = Vec::new();
201 attrs.extend([EGL_PLATFORM_ANGLE_TYPE_ANGLE, backend]);
202 if software {
203 attrs.extend([EGL_PLATFORM_ANGLE_DEVICE_TYPE_ANGLE, EGL_PLATFORM_ANGLE_DEVICE_TYPE_SWIFTSHADER_ANGLE]);
204 }
205 let mut native_display = std::ptr::null_mut();
206 if cfg!(all(unix, not(target_os = "macos"))) {
207 match window {
208 Some(NativeWindow::Xlib(d, _)) => {
209 attrs.extend([EGL_PLATFORM_ANGLE_NATIVE_PLATFORM_TYPE_ANGLE, EGL_PLATFORM_X11_EXT]);
210 native_display = d;
211 }
212 Some(NativeWindow::Wayland(d)) => {
213 attrs.extend([EGL_PLATFORM_ANGLE_NATIVE_PLATFORM_TYPE_ANGLE, EGL_PLATFORM_WAYLAND_EXT]);
214 native_display = d;
215 }
216 _ => attrs.extend([EGL_PLATFORM_ANGLE_NATIVE_PLATFORM_TYPE_ANGLE, EGL_PLATFORM_SURFACELESS_MESA]),
217 }
218 }
219 attrs.push(EGL_NONE as Attrib);
220 let display = (egl.GetPlatformDisplay)(EGL_PLATFORM_ANGLE_ANGLE, native_display, attrs.as_ptr());
221 if display.is_null() {
222 return Err(format!("ANGLE: no display (EGL error {:#x})", (egl.GetError)()));
223 }
224 let (mut major, mut minor) = (0, 0);
225 if (egl.Initialize)(display, &mut major, &mut minor) != EGL_TRUE {
226 return Err(format!("ANGLE: eglInitialize failed (EGL error {:#x})", (egl.GetError)()));
227 }
228 let exts = cstr((egl.QueryString)(display, EGL_EXTENSIONS));
229 if !exts.contains("EGL_ANGLE_create_context_webgl_compatibility") {
230 (egl.Terminate)(display);
231 return Err("ANGLE: no WebGL compatibility contexts (EGL_ANGLE_create_context_webgl_compatibility)".into());
232 }
233 let config = choose_config(&egl, display, if window.is_some() { EGL_WINDOW_BIT } else { EGL_PBUFFER_BIT })?;
234 let mut ctx_attrs = vec![
235 EGL_CONTEXT_MAJOR_VERSION, 3, EGL_CONTEXT_MINOR_VERSION, 0,
236 EGL_CONTEXT_WEBGL_COMPATIBILITY_ANGLE, 1,
237 ];
238 if exts.contains("EGL_ANGLE_robust_resource_initialization") {
239 ctx_attrs.extend([EGL_ROBUST_RESOURCE_INITIALIZATION_ANGLE, 1]);
240 }
241 if exts.contains("EGL_EXT_create_context_robustness") {
242 ctx_attrs.extend([EGL_CONTEXT_OPENGL_ROBUST_ACCESS_EXT, 1]);
243 }
244 ctx_attrs.push(EGL_NONE);
245 let context = (egl.CreateContext)(display, config, std::ptr::null_mut(), ctx_attrs.as_ptr());
246 if context.is_null() {
247 let e = (egl.GetError)();
248 (egl.Terminate)(display);
249 return Err(format!("ANGLE: cannot create a GLES 3.0 context (EGL error {e:#x})"));
250 }
251 let surf = surface(&egl, display, config);
252 if surf.is_null() {
253 let e = (egl.GetError)();
254 (egl.DestroyContext)(display, context);
255 (egl.Terminate)(display);
256 return Err(format!("ANGLE: cannot create the surface (EGL error {e:#x})"));
257 }
258 if (egl.MakeCurrent)(display, surf, surf, context) != EGL_TRUE {
259 return Err(format!("ANGLE: eglMakeCurrent failed (EGL error {:#x})", (egl.GetError)()));
260 }
261 if window.is_some() {
262 (egl.SwapInterval)(display, 0); }
264 let get = egl.GetProcAddress;
265 let gl = Gles::load(|name: &CStr| get(name.as_ptr()))?;
266 let renderer = cstr((gl.glGetString)(0x1F01) as *const c_char);
267 let version = cstr((egl.QueryString)(display, EGL_VERSION));
268 let renderer = format!("{renderer} (EGL {version})");
269 Ok(Angle { _lib: lib, egl, display, context, surface: surf, gl, renderer })
270 }
271 }
272
273 pub fn size(&self) -> (u32, u32) {
275 let (mut w, mut h) = (0, 0);
276 unsafe {
277 (self.egl.QuerySurface)(self.display, self.surface, EGL_WIDTH, &mut w);
278 (self.egl.QuerySurface)(self.display, self.surface, EGL_HEIGHT, &mut h);
279 }
280 (w.max(0) as u32, h.max(0) as u32)
281 }
282
283 pub fn swap(&self) {
284 unsafe {
285 (self.egl.SwapBuffers)(self.display, self.surface);
286 }
287 }
288
289 pub fn make_current(&self) {
290 unsafe {
291 (self.egl.MakeCurrent)(self.display, self.surface, self.surface, self.context);
292 }
293 }
294
295 pub fn read_frame(&self) -> (u32, u32, Vec<u8>) {
297 let (w, h) = self.size();
298 let mut px = vec![0u8; w as usize * h as usize * 4];
299 unsafe {
300 let g = &self.gl;
301 let (mut fb, mut pack, mut align) = (0, 0, 0);
302 (g.glGetIntegerv)(0x8CAA, &mut fb as *mut i32 as *mut c_void); (g.glGetIntegerv)(0x88ED, &mut pack as *mut i32 as *mut c_void); (g.glGetIntegerv)(0x0D05, &mut align as *mut i32 as *mut c_void); (g.glBindFramebuffer)(0x8CA8, 0);
306 (g.glBindBuffer)(0x88EB, 0);
307 (g.glPixelStorei)(0x0D05, 1);
308 (g.glReadPixels)(0, 0, w as i32, h as i32, 0x1908, 0x1401, px.as_mut_ptr() as *mut c_void);
309 (g.glPixelStorei)(0x0D05, align);
310 (g.glBindBuffer)(0x88EB, pack as u32);
311 (g.glBindFramebuffer)(0x8CA8, fb as u32);
312 }
313 let row = w as usize * 4;
314 let mut flipped = Vec::with_capacity(px.len());
315 for y in (0..h as usize).rev() {
316 flipped.extend_from_slice(&px[y * row..(y + 1) * row]);
317 }
318 (w, h, flipped)
319 }
320}
321
322impl Drop for Angle {
323 fn drop(&mut self) {
324 unsafe {
325 (self.egl.MakeCurrent)(self.display, std::ptr::null_mut(), std::ptr::null_mut(), std::ptr::null_mut());
326 (self.egl.DestroySurface)(self.display, self.surface);
327 (self.egl.DestroyContext)(self.display, self.context);
328 (self.egl.Terminate)(self.display);
329 }
330 }
331}
332
333unsafe fn choose_config(egl: &Egl, display: EglDisplay, surface_bit: i32) -> Result<EglConfig, String> {
334 for alpha in [0, 8] {
335 let attrs = [
336 EGL_RED_SIZE, 8, EGL_GREEN_SIZE, 8, EGL_BLUE_SIZE, 8, EGL_ALPHA_SIZE, alpha,
337 EGL_DEPTH_SIZE, 24, EGL_STENCIL_SIZE, 8, EGL_SAMPLES, 0,
338 EGL_SURFACE_TYPE, surface_bit, EGL_RENDERABLE_TYPE, EGL_OPENGL_ES3_BIT, EGL_NONE,
339 ];
340 let mut config = std::ptr::null_mut();
341 let mut n = 0;
342 if unsafe { (egl.ChooseConfig)(display, attrs.as_ptr(), &mut config, 1, &mut n) } == EGL_TRUE && n > 0 {
343 return Ok(config);
344 }
345 }
346 Err("ANGLE: no RGB8 + depth 24 + stencil 8 config".into())
347}
348
349unsafe fn load_egl(lib: &libloading::Library) -> Result<Egl, String> {
350 unsafe fn sym<T: Copy>(lib: &libloading::Library, name: &str) -> Result<T, String> {
351 let s: libloading::Symbol<T> = unsafe { lib.get(name.as_bytes()) }.map_err(|e| format!("ANGLE: {name}: {e}"))?;
352 Ok(*s)
353 }
354 unsafe {
355 Ok(Egl {
356 GetProcAddress: sym(lib, "eglGetProcAddress")?,
357 GetPlatformDisplay: sym(lib, "eglGetPlatformDisplay")?,
358 Initialize: sym(lib, "eglInitialize")?,
359 Terminate: sym(lib, "eglTerminate")?,
360 QueryString: sym(lib, "eglQueryString")?,
361 ChooseConfig: sym(lib, "eglChooseConfig")?,
362 CreateContext: sym(lib, "eglCreateContext")?,
363 DestroyContext: sym(lib, "eglDestroyContext")?,
364 CreatePbufferSurface: sym(lib, "eglCreatePbufferSurface")?,
365 CreateWindowSurface: sym(lib, "eglCreateWindowSurface")?,
366 DestroySurface: sym(lib, "eglDestroySurface")?,
367 QuerySurface: sym(lib, "eglQuerySurface")?,
368 MakeCurrent: sym(lib, "eglMakeCurrent")?,
369 SwapBuffers: sym(lib, "eglSwapBuffers")?,
370 SwapInterval: sym(lib, "eglSwapInterval")?,
371 GetError: sym(lib, "eglGetError")?,
372 })
373 }
374}
375
376fn cstr(p: *const c_char) -> String {
377 if p.is_null() {
378 return String::new();
379 }
380 unsafe { CStr::from_ptr(p) }.to_string_lossy().into_owned()
381}
382
383pub fn c_string(s: &str) -> CString {
385 CString::new(s.replace('\0', "")).expect("no NUL")
386}
387
388fn choose(software: bool, open: impl Fn(Attrib) -> Result<Angle, String>) -> Result<Angle, String> {
393 const OPENGL: Attrib = 0x320D; let chosen = std::env::var("GASM_ANGLE_BACKEND").ok();
395 let backend = match chosen.as_deref() {
396 Some("metal") => EGL_PLATFORM_ANGLE_TYPE_METAL_ANGLE,
397 Some("opengl") => OPENGL,
398 Some("vulkan") => EGL_PLATFORM_ANGLE_TYPE_VULKAN_ANGLE,
399 Some("d3d11") => EGL_PLATFORM_ANGLE_TYPE_D3D11_ANGLE,
400 Some(other) => return Err(format!("GASM_ANGLE_BACKEND={other}: use metal, opengl, vulkan or d3d11")),
401 None if software => EGL_PLATFORM_ANGLE_TYPE_VULKAN_ANGLE,
402 None if cfg!(target_os = "macos") => EGL_PLATFORM_ANGLE_TYPE_METAL_ANGLE,
403 None if cfg!(windows) => EGL_PLATFORM_ANGLE_TYPE_D3D11_ANGLE,
404 None => EGL_PLATFORM_ANGLE_TYPE_VULKAN_ANGLE,
405 };
406 let a = open(backend)?;
407 if chosen.is_none() && backend == EGL_PLATFORM_ANGLE_TYPE_METAL_ANGLE && a.renderer.contains("Paravirtual") {
408 drop(a);
409 eprintln!("[gasm] gl: paravirtual GPU (a virtual machine): ANGLE on OpenGL instead of Metal");
410 return open(OPENGL);
411 }
412 Ok(a)
413}
414
415#[cfg(target_os = "macos")]
417fn layer_of(view: *mut c_void) -> *mut c_void {
418 #[link(name = "objc")]
419 unsafe extern "C" {
420 fn sel_registerName(name: *const c_char) -> *mut c_void;
421 fn objc_msgSend();
422 }
423 unsafe {
424 let send_bool: unsafe extern "C" fn(*mut c_void, *mut c_void, u8) = std::mem::transmute(objc_msgSend as unsafe extern "C" fn());
425 let send_ptr: unsafe extern "C" fn(*mut c_void, *mut c_void) -> *mut c_void = std::mem::transmute(objc_msgSend as unsafe extern "C" fn());
426 send_bool(view, sel_registerName(c"setWantsLayer:".as_ptr()), 1);
427 send_ptr(view, sel_registerName(c"layer".as_ptr()))
428 }
429}
430
431#[cfg(not(target_os = "macos"))]
432fn layer_of(view: *mut c_void) -> *mut c_void {
433 view
434}
435
436#[cfg(test)]
437mod tests {
438 use super::*;
439
440 #[test]
442 fn offscreen_context() {
443 let Ok(dir) = find(None) else { return eprintln!("ANGLE not found: skipped") };
444 let a = Angle::offscreen(&dir, 64, 32, false).or_else(|_| Angle::offscreen(&dir, 64, 32, true)).expect("context");
445 assert_eq!(a.size(), (64, 32));
446 eprintln!("{}", a.renderer);
447 unsafe {
448 (a.gl.glClearColor)(1.0, 0.5, 0.0, 1.0);
449 (a.gl.glClear)(0x4000);
450 }
451 let (w, h, px) = a.read_frame();
452 assert_eq!((w, h), (64, 32));
453 assert_eq!(&px[..4], &[255, 128, 0, 255]);
454 }
455}