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 show_image(&self, rgba: &[u8], w: u32, h: u32) {
287 const TEXTURE_2D: u32 = 0x0DE1;
288 const READ_FRAMEBUFFER: u32 = 0x8CA8;
289 const DRAW_FRAMEBUFFER: u32 = 0x8CA9;
290 const COLOR_ATTACHMENT0: u32 = 0x8CE0;
291 const SCISSOR_TEST: u32 = 0x0C11;
292 let g = &self.gl;
293 let (dw, dh) = self.size();
294 let (ox, oy, sx, sy) = crate::present::letterbox((dw as f64, dh as f64), (w as f64, h as f64), true, None);
295 let (x0, x1) = (ox.round() as i32, (ox + sx * w as f64).round() as i32);
296 let (y0, y1) = ((dh as f64 - oy).round() as i32, (dh as f64 - oy - sy * h as f64).round() as i32);
298 unsafe {
299 let (mut tex, mut fbo) = (0u32, 0u32);
300 (g.glGenTextures)(1, &mut tex as *mut u32 as *mut c_void);
301 (g.glBindTexture)(TEXTURE_2D, tex);
302 (g.glPixelStorei)(0x0CF5, 1); (g.glTexImage2D)(TEXTURE_2D, 0, 0x8058 as i32, w as i32, h as i32, 0, 0x1908 , 0x1401 , rgba.as_ptr() as *const c_void);
304 (g.glPixelStorei)(0x0CF5, 4);
305 (g.glGenFramebuffers)(1, &mut fbo as *mut u32 as *mut c_void);
306 (g.glBindFramebuffer)(READ_FRAMEBUFFER, fbo);
307 (g.glFramebufferTexture2D)(READ_FRAMEBUFFER, COLOR_ATTACHMENT0, TEXTURE_2D, tex, 0);
308 (g.glBindFramebuffer)(DRAW_FRAMEBUFFER, 0);
309 (g.glDisable)(SCISSOR_TEST);
310 (g.glClearColor)(0.0, 0.0, 0.0, 1.0);
311 (g.glClear)(0x4000); (g.glBlitFramebuffer)(0, 0, w as i32, h as i32, x0, y0, x1, y1, 0x4000, 0x2600 );
313 (g.glBindFramebuffer)(READ_FRAMEBUFFER, 0);
314 (g.glBindTexture)(TEXTURE_2D, 0);
315 (g.glDeleteFramebuffers)(1, &fbo as *const u32 as *const c_void);
316 (g.glDeleteTextures)(1, &tex as *const u32 as *const c_void);
317 }
318 self.swap();
319 }
320
321 pub fn show_image_over_game(&self, rgba: &[u8], w: u32, h: u32) {
324 const ACTIVE_TEXTURE: u32 = 0x84E0;
325 const TEXTURE0: u32 = 0x84C0;
326 const TEXTURE_BINDING_2D: u32 = 0x8069;
327 const READ_FRAMEBUFFER_BINDING: u32 = 0x8CAA;
328 const DRAW_FRAMEBUFFER_BINDING: u32 = 0x8CA6;
329 const READ_FRAMEBUFFER: u32 = 0x8CA8;
330 const DRAW_FRAMEBUFFER: u32 = 0x8CA9;
331 const PIXEL_UNPACK_BUFFER: u32 = 0x88EC;
332 const PIXEL_UNPACK_BUFFER_BINDING: u32 = 0x88EF;
333 const UNPACK: [u32; 5] = [0x0CF5, 0x0CF2, 0x0CF3, 0x0CF4, 0x806E]; const SCISSOR_TEST: u32 = 0x0C11;
335 const COLOR_CLEAR_VALUE: u32 = 0x0C22;
336 const COLOR_WRITEMASK: u32 = 0x0C23;
337 let g = &self.gl;
338 let int = |p: u32| {
339 let mut v = 0i32;
340 unsafe { (g.glGetIntegerv)(p, &mut v as *mut i32 as *mut c_void) };
341 v
342 };
343 let (active, read_fb, draw_fb, unpack_buf) = (int(ACTIVE_TEXTURE), int(READ_FRAMEBUFFER_BINDING), int(DRAW_FRAMEBUFFER_BINDING), int(PIXEL_UNPACK_BUFFER_BINDING));
344 let unpack = UNPACK.map(int);
345 let mut clear = [0f32; 4];
346 let mut mask = [0i32; 4];
347 unsafe {
348 (g.glActiveTexture)(TEXTURE0);
349 let tex0 = int(TEXTURE_BINDING_2D);
350 let scissor = (g.glIsEnabled)(SCISSOR_TEST) != 0;
351 (g.glGetFloatv)(COLOR_CLEAR_VALUE, clear.as_mut_ptr() as *mut c_void);
352 (g.glGetIntegerv)(COLOR_WRITEMASK, mask.as_mut_ptr() as *mut c_void);
353 (g.glBindBuffer)(PIXEL_UNPACK_BUFFER, 0);
354 for p in &UNPACK[1..] {
355 (g.glPixelStorei)(*p, 0);
356 }
357 (g.glColorMask)(1, 1, 1, 1);
358 self.show_image(rgba, w, h); (g.glColorMask)(mask[0] as u8, mask[1] as u8, mask[2] as u8, mask[3] as u8);
360 for (p, v) in UNPACK.iter().zip(unpack) {
361 (g.glPixelStorei)(*p, v);
362 }
363 (g.glBindBuffer)(PIXEL_UNPACK_BUFFER, unpack_buf as u32);
364 (g.glClearColor)(clear[0], clear[1], clear[2], clear[3]);
365 if scissor {
366 (g.glEnable)(SCISSOR_TEST);
367 }
368 (g.glBindTexture)(0x0DE1, tex0 as u32);
369 (g.glActiveTexture)(active as u32);
370 (g.glBindFramebuffer)(READ_FRAMEBUFFER, read_fb as u32);
371 (g.glBindFramebuffer)(DRAW_FRAMEBUFFER, draw_fb as u32);
372 }
373 }
374
375 pub fn swap(&self) {
376 unsafe {
377 (self.egl.SwapBuffers)(self.display, self.surface);
378 }
379 }
380
381 pub fn make_current(&self) {
382 unsafe {
383 (self.egl.MakeCurrent)(self.display, self.surface, self.surface, self.context);
384 }
385 }
386
387 pub fn read_frame(&self) -> (u32, u32, Vec<u8>) {
389 let (w, h) = self.size();
390 let mut px = vec![0u8; w as usize * h as usize * 4];
391 unsafe {
392 let g = &self.gl;
393 let (mut fb, mut pack, mut align) = (0, 0, 0);
394 (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);
398 (g.glBindBuffer)(0x88EB, 0);
399 (g.glPixelStorei)(0x0D05, 1);
400 (g.glReadPixels)(0, 0, w as i32, h as i32, 0x1908, 0x1401, px.as_mut_ptr() as *mut c_void);
401 (g.glPixelStorei)(0x0D05, align);
402 (g.glBindBuffer)(0x88EB, pack as u32);
403 (g.glBindFramebuffer)(0x8CA8, fb as u32);
404 }
405 let row = w as usize * 4;
406 let mut flipped = Vec::with_capacity(px.len());
407 for y in (0..h as usize).rev() {
408 flipped.extend_from_slice(&px[y * row..(y + 1) * row]);
409 }
410 (w, h, flipped)
411 }
412}
413
414impl Drop for Angle {
415 fn drop(&mut self) {
416 unsafe {
417 (self.egl.MakeCurrent)(self.display, std::ptr::null_mut(), std::ptr::null_mut(), std::ptr::null_mut());
418 (self.egl.DestroySurface)(self.display, self.surface);
419 (self.egl.DestroyContext)(self.display, self.context);
420 (self.egl.Terminate)(self.display);
421 }
422 }
423}
424
425unsafe fn choose_config(egl: &Egl, display: EglDisplay, surface_bit: i32) -> Result<EglConfig, String> {
426 for alpha in [0, 8] {
427 let attrs = [
428 EGL_RED_SIZE, 8, EGL_GREEN_SIZE, 8, EGL_BLUE_SIZE, 8, EGL_ALPHA_SIZE, alpha,
429 EGL_DEPTH_SIZE, 24, EGL_STENCIL_SIZE, 8, EGL_SAMPLES, 0,
430 EGL_SURFACE_TYPE, surface_bit, EGL_RENDERABLE_TYPE, EGL_OPENGL_ES3_BIT, EGL_NONE,
431 ];
432 let mut config = std::ptr::null_mut();
433 let mut n = 0;
434 if unsafe { (egl.ChooseConfig)(display, attrs.as_ptr(), &mut config, 1, &mut n) } == EGL_TRUE && n > 0 {
435 return Ok(config);
436 }
437 }
438 Err("ANGLE: no RGB8 + depth 24 + stencil 8 config".into())
439}
440
441unsafe fn load_egl(lib: &libloading::Library) -> Result<Egl, String> {
442 unsafe fn sym<T: Copy>(lib: &libloading::Library, name: &str) -> Result<T, String> {
443 let s: libloading::Symbol<T> = unsafe { lib.get(name.as_bytes()) }.map_err(|e| format!("ANGLE: {name}: {e}"))?;
444 Ok(*s)
445 }
446 unsafe {
447 Ok(Egl {
448 GetProcAddress: sym(lib, "eglGetProcAddress")?,
449 GetPlatformDisplay: sym(lib, "eglGetPlatformDisplay")?,
450 Initialize: sym(lib, "eglInitialize")?,
451 Terminate: sym(lib, "eglTerminate")?,
452 QueryString: sym(lib, "eglQueryString")?,
453 ChooseConfig: sym(lib, "eglChooseConfig")?,
454 CreateContext: sym(lib, "eglCreateContext")?,
455 DestroyContext: sym(lib, "eglDestroyContext")?,
456 CreatePbufferSurface: sym(lib, "eglCreatePbufferSurface")?,
457 CreateWindowSurface: sym(lib, "eglCreateWindowSurface")?,
458 DestroySurface: sym(lib, "eglDestroySurface")?,
459 QuerySurface: sym(lib, "eglQuerySurface")?,
460 MakeCurrent: sym(lib, "eglMakeCurrent")?,
461 SwapBuffers: sym(lib, "eglSwapBuffers")?,
462 SwapInterval: sym(lib, "eglSwapInterval")?,
463 GetError: sym(lib, "eglGetError")?,
464 })
465 }
466}
467
468fn cstr(p: *const c_char) -> String {
469 if p.is_null() {
470 return String::new();
471 }
472 unsafe { CStr::from_ptr(p) }.to_string_lossy().into_owned()
473}
474
475pub fn c_string(s: &str) -> CString {
477 CString::new(s.replace('\0', "")).expect("no NUL")
478}
479
480fn choose(software: bool, open: impl Fn(Attrib) -> Result<Angle, String>) -> Result<Angle, String> {
485 const OPENGL: Attrib = 0x320D; let chosen = std::env::var("GASM_ANGLE_BACKEND").ok();
487 let backend = match chosen.as_deref() {
488 Some("metal") => EGL_PLATFORM_ANGLE_TYPE_METAL_ANGLE,
489 Some("opengl") => OPENGL,
490 Some("vulkan") => EGL_PLATFORM_ANGLE_TYPE_VULKAN_ANGLE,
491 Some("d3d11") => EGL_PLATFORM_ANGLE_TYPE_D3D11_ANGLE,
492 Some(other) => return Err(format!("GASM_ANGLE_BACKEND={other}: use metal, opengl, vulkan or d3d11")),
493 None if software => EGL_PLATFORM_ANGLE_TYPE_VULKAN_ANGLE,
494 None if cfg!(target_os = "macos") => EGL_PLATFORM_ANGLE_TYPE_METAL_ANGLE,
495 None if cfg!(windows) => EGL_PLATFORM_ANGLE_TYPE_D3D11_ANGLE,
496 None => EGL_PLATFORM_ANGLE_TYPE_VULKAN_ANGLE,
497 };
498 let a = open(backend)?;
499 if chosen.is_none() && backend == EGL_PLATFORM_ANGLE_TYPE_METAL_ANGLE && a.renderer.contains("Paravirtual") {
500 drop(a);
501 eprintln!("[gasm] gl: paravirtual GPU (a virtual machine): ANGLE on OpenGL instead of Metal");
502 return open(OPENGL);
503 }
504 Ok(a)
505}
506
507#[cfg(target_os = "macos")]
509fn layer_of(view: *mut c_void) -> *mut c_void {
510 #[link(name = "objc")]
511 unsafe extern "C" {
512 fn sel_registerName(name: *const c_char) -> *mut c_void;
513 fn objc_msgSend();
514 }
515 unsafe {
516 let send_bool: unsafe extern "C" fn(*mut c_void, *mut c_void, u8) = std::mem::transmute(objc_msgSend as unsafe extern "C" fn());
517 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());
518 send_bool(view, sel_registerName(c"setWantsLayer:".as_ptr()), 1);
519 send_ptr(view, sel_registerName(c"layer".as_ptr()))
520 }
521}
522
523#[cfg(not(target_os = "macos"))]
524fn layer_of(view: *mut c_void) -> *mut c_void {
525 view
526}
527
528#[cfg(test)]
529mod tests {
530 use super::*;
531
532 #[test]
534 fn offscreen_context() {
535 let Ok(dir) = find(None) else { return eprintln!("ANGLE not found: skipped") };
536 let a = Angle::offscreen(&dir, 64, 32, false).or_else(|_| Angle::offscreen(&dir, 64, 32, true)).expect("context");
537 assert_eq!(a.size(), (64, 32));
538 eprintln!("{}", a.renderer);
539 unsafe {
540 (a.gl.glClearColor)(1.0, 0.5, 0.0, 1.0);
541 (a.gl.glClear)(0x4000);
542 }
543 let (w, h, px) = a.read_frame();
544 assert_eq!((w, h), (64, 32));
545 assert_eq!(&px[..4], &[255, 128, 0, 255]);
546 }
547}