1use glow::HasContext;
2use parking_lot::{Mutex, MutexGuard};
3
4use std::{ffi, os::raw, ptr, sync::Arc, time::Duration};
5
6const CONTEXT_LOCK_TIMEOUT_SECS: u64 = 1;
8
9const EGL_CONTEXT_FLAGS_KHR: i32 = 0x30FC;
10const EGL_CONTEXT_OPENGL_DEBUG_BIT_KHR: i32 = 0x0001;
11const EGL_CONTEXT_OPENGL_ROBUST_ACCESS_EXT: i32 = 0x30BF;
12const EGL_PLATFORM_WAYLAND_KHR: u32 = 0x31D8;
13const EGL_PLATFORM_X11_KHR: u32 = 0x31D5;
14const EGL_PLATFORM_ANGLE_ANGLE: u32 = 0x3202;
15const EGL_PLATFORM_ANGLE_NATIVE_PLATFORM_TYPE_ANGLE: u32 = 0x348F;
16const EGL_PLATFORM_ANGLE_DEBUG_LAYERS_ENABLED: u32 = 0x3451;
17const EGL_PLATFORM_SURFACELESS_MESA: u32 = 0x31DD;
18const EGL_GL_COLORSPACE_KHR: u32 = 0x309D;
19const EGL_GL_COLORSPACE_SRGB_KHR: u32 = 0x3089;
20
21type XOpenDisplayFun =
22 unsafe extern "system" fn(display_name: *const raw::c_char) -> *mut raw::c_void;
23
24type XCloseDisplayFun = unsafe extern "system" fn(display: *mut raw::c_void) -> raw::c_int;
25
26type WlDisplayConnectFun =
27 unsafe extern "system" fn(display_name: *const raw::c_char) -> *mut raw::c_void;
28
29type WlDisplayDisconnectFun = unsafe extern "system" fn(display: *const raw::c_void);
30
31#[cfg(not(target_os = "emscripten"))]
32type EglInstance = khronos_egl::DynamicInstance<khronos_egl::EGL1_4>;
33
34#[cfg(target_os = "emscripten")]
35type EglInstance = khronos_egl::Instance<khronos_egl::Static>;
36
37type WlEglWindowCreateFun = unsafe extern "system" fn(
38 surface: *const raw::c_void,
39 width: raw::c_int,
40 height: raw::c_int,
41) -> *mut raw::c_void;
42
43type WlEglWindowResizeFun = unsafe extern "system" fn(
44 window: *const raw::c_void,
45 width: raw::c_int,
46 height: raw::c_int,
47 dx: raw::c_int,
48 dy: raw::c_int,
49);
50
51type WlEglWindowDestroyFun = unsafe extern "system" fn(window: *const raw::c_void);
52
53#[cfg(target_os = "android")]
54extern "C" {
55 pub fn ANativeWindow_setBuffersGeometry(
56 window: *mut raw::c_void,
57 width: i32,
58 height: i32,
59 format: i32,
60 ) -> i32;
61}
62
63type EglLabel = *const raw::c_void;
64
65#[allow(clippy::upper_case_acronyms)]
66type EGLDEBUGPROCKHR = Option<
67 unsafe extern "system" fn(
68 error: khronos_egl::Enum,
69 command: *const raw::c_char,
70 message_type: u32,
71 thread_label: EglLabel,
72 object_label: EglLabel,
73 message: *const raw::c_char,
74 ),
75>;
76
77const EGL_DEBUG_MSG_CRITICAL_KHR: u32 = 0x33B9;
78const EGL_DEBUG_MSG_ERROR_KHR: u32 = 0x33BA;
79const EGL_DEBUG_MSG_WARN_KHR: u32 = 0x33BB;
80const EGL_DEBUG_MSG_INFO_KHR: u32 = 0x33BC;
81
82type EglDebugMessageControlFun = unsafe extern "system" fn(
83 proc: EGLDEBUGPROCKHR,
84 attrib_list: *const khronos_egl::Attrib,
85) -> raw::c_int;
86
87unsafe extern "system" fn egl_debug_proc(
88 error: khronos_egl::Enum,
89 command_raw: *const raw::c_char,
90 message_type: u32,
91 _thread_label: EglLabel,
92 _object_label: EglLabel,
93 message_raw: *const raw::c_char,
94) {
95 let log_severity = match message_type {
96 EGL_DEBUG_MSG_CRITICAL_KHR | EGL_DEBUG_MSG_ERROR_KHR => log::Level::Error,
97 EGL_DEBUG_MSG_WARN_KHR => log::Level::Warn,
98 EGL_DEBUG_MSG_INFO_KHR => log::Level::Info,
99 _ => log::Level::Debug,
100 };
101 let command = unsafe { ffi::CStr::from_ptr(command_raw) }.to_string_lossy();
102 let message = if message_raw.is_null() {
103 "".into()
104 } else {
105 unsafe { ffi::CStr::from_ptr(message_raw) }.to_string_lossy()
106 };
107
108 log::log!(
109 log_severity,
110 "EGL '{}' code 0x{:x}: {}",
111 command,
112 error,
113 message,
114 );
115}
116
117#[derive(Debug)]
122enum DisplayRef {
123 X11(ptr::NonNull<raw::c_void>),
124 Wayland,
125}
126
127impl DisplayRef {
128 fn as_ptr(&self) -> *mut raw::c_void {
130 match *self {
131 Self::X11(ptr) => ptr.as_ptr(),
132 Self::Wayland => unreachable!(),
133 }
134 }
135}
136
137#[derive(Debug)]
143struct DisplayOwner {
144 library: libloading::Library,
145 display: DisplayRef,
146}
147
148impl Drop for DisplayOwner {
149 fn drop(&mut self) {
150 match self.display {
151 DisplayRef::X11(ptr) => unsafe {
152 let func: libloading::Symbol<XCloseDisplayFun> =
153 self.library.get(b"XCloseDisplay").unwrap();
154 func(ptr.as_ptr());
155 },
156 DisplayRef::Wayland => {}
157 }
158 }
159}
160
161fn open_x_display() -> Option<DisplayOwner> {
162 log::info!("Loading X11 library to get the current display");
163 unsafe {
164 let library = libloading::Library::new("libX11.so").ok()?;
165 let func: libloading::Symbol<XOpenDisplayFun> = library.get(b"XOpenDisplay").unwrap();
166 let result = func(ptr::null());
167 ptr::NonNull::new(result).map(|ptr| DisplayOwner {
168 display: DisplayRef::X11(ptr),
169 library,
170 })
171 }
172}
173
174unsafe fn find_library(paths: &[&str]) -> Option<libloading::Library> {
175 for path in paths {
176 match unsafe { libloading::Library::new(path) } {
177 Ok(lib) => return Some(lib),
178 _ => continue,
179 };
180 }
181 None
182}
183
184fn test_wayland_display() -> Option<DisplayOwner> {
185 log::info!("Loading Wayland library to get the current display");
189 let library = unsafe {
190 let client_library = find_library(&["libwayland-client.so.0", "libwayland-client.so"])?;
191 let wl_display_connect: libloading::Symbol<WlDisplayConnectFun> =
192 client_library.get(b"wl_display_connect").unwrap();
193 let wl_display_disconnect: libloading::Symbol<WlDisplayDisconnectFun> =
194 client_library.get(b"wl_display_disconnect").unwrap();
195 let display = ptr::NonNull::new(wl_display_connect(ptr::null()))?;
196 wl_display_disconnect(display.as_ptr());
197 find_library(&["libwayland-egl.so.1", "libwayland-egl.so"])?
198 };
199 Some(DisplayOwner {
200 library,
201 display: DisplayRef::Wayland,
202 })
203}
204
205#[derive(Clone, Copy, Debug)]
206enum SrgbFrameBufferKind {
207 None,
209 Core,
211 Khr,
213}
214
215fn choose_config(
217 egl: &EglInstance,
218 display: khronos_egl::Display,
219 srgb_kind: SrgbFrameBufferKind,
220) -> Result<(khronos_egl::Config, bool), crate::InstanceError> {
221 let tiers = [
223 (
224 "off-screen",
225 &[
226 khronos_egl::SURFACE_TYPE,
227 khronos_egl::PBUFFER_BIT,
228 khronos_egl::RENDERABLE_TYPE,
229 khronos_egl::OPENGL_ES2_BIT,
230 ][..],
231 ),
232 (
233 "presentation",
234 &[khronos_egl::SURFACE_TYPE, khronos_egl::WINDOW_BIT][..],
235 ),
236 #[cfg(not(target_os = "android"))]
237 (
238 "native-render",
239 &[khronos_egl::NATIVE_RENDERABLE, khronos_egl::TRUE as _][..],
240 ),
241 ];
242
243 let mut attributes = Vec::with_capacity(9);
244 for tier_max in (0..tiers.len()).rev() {
245 let name = tiers[tier_max].0;
246 log::info!("\tTrying {}", name);
247
248 attributes.clear();
249 for &(_, tier_attr) in tiers[..=tier_max].iter() {
250 attributes.extend_from_slice(tier_attr);
251 }
252 match srgb_kind {
254 SrgbFrameBufferKind::None => {}
255 _ => {
256 attributes.push(khronos_egl::ALPHA_SIZE);
257 attributes.push(8);
258 }
259 }
260 attributes.push(khronos_egl::NONE);
261
262 match egl.choose_first_config(display, &attributes) {
263 Ok(Some(config)) => {
264 if tier_max == 1 {
265 log::warn!("EGL says it can present to the window but not natively",);
268 }
269 let tier_threshold = if cfg!(target_os = "android") || cfg!(windows) {
271 1
272 } else {
273 2
274 };
275 return Ok((config, tier_max >= tier_threshold));
276 }
277 Ok(None) => {
278 log::warn!("No config found!");
279 }
280 Err(e) => {
281 log::error!("error in choose_first_config: {:?}", e);
282 }
283 }
284 }
285
286 Err(crate::InstanceError)
287}
288
289fn gl_debug_message_callback(source: u32, gltype: u32, id: u32, severity: u32, message: &str) {
290 let source_str = match source {
291 glow::DEBUG_SOURCE_API => "API",
292 glow::DEBUG_SOURCE_WINDOW_SYSTEM => "Window System",
293 glow::DEBUG_SOURCE_SHADER_COMPILER => "ShaderCompiler",
294 glow::DEBUG_SOURCE_THIRD_PARTY => "Third Party",
295 glow::DEBUG_SOURCE_APPLICATION => "Application",
296 glow::DEBUG_SOURCE_OTHER => "Other",
297 _ => unreachable!(),
298 };
299
300 let log_severity = match severity {
301 glow::DEBUG_SEVERITY_HIGH => log::Level::Error,
302 glow::DEBUG_SEVERITY_MEDIUM => log::Level::Warn,
303 glow::DEBUG_SEVERITY_LOW => log::Level::Info,
304 glow::DEBUG_SEVERITY_NOTIFICATION => log::Level::Trace,
305 _ => unreachable!(),
306 };
307
308 let type_str = match gltype {
309 glow::DEBUG_TYPE_DEPRECATED_BEHAVIOR => "Deprecated Behavior",
310 glow::DEBUG_TYPE_ERROR => "Error",
311 glow::DEBUG_TYPE_MARKER => "Marker",
312 glow::DEBUG_TYPE_OTHER => "Other",
313 glow::DEBUG_TYPE_PERFORMANCE => "Performance",
314 glow::DEBUG_TYPE_POP_GROUP => "Pop Group",
315 glow::DEBUG_TYPE_PORTABILITY => "Portability",
316 glow::DEBUG_TYPE_PUSH_GROUP => "Push Group",
317 glow::DEBUG_TYPE_UNDEFINED_BEHAVIOR => "Undefined Behavior",
318 _ => unreachable!(),
319 };
320
321 let _ = std::panic::catch_unwind(|| {
322 log::log!(
323 log_severity,
324 "GLES: [{}/{}] ID {} : {}",
325 source_str,
326 type_str,
327 id,
328 message
329 );
330 });
331
332 if cfg!(debug_assertions) && log_severity == log::Level::Error {
333 crate::VALIDATION_CANARY.set();
335 }
336}
337
338#[derive(Clone, Debug)]
339struct EglContext {
340 instance: Arc<EglInstance>,
341 version: (i32, i32),
342 display: khronos_egl::Display,
343 raw: khronos_egl::Context,
344 pbuffer: Option<khronos_egl::Surface>,
345}
346
347impl EglContext {
348 fn make_current(&self) {
349 self.instance
350 .make_current(self.display, self.pbuffer, self.pbuffer, Some(self.raw))
351 .unwrap();
352 }
353 fn unmake_current(&self) {
354 self.instance
355 .make_current(self.display, None, None, None)
356 .unwrap();
357 }
358}
359
360pub struct AdapterContext {
363 glow: Mutex<glow::Context>,
364 egl: Option<EglContext>,
365}
366
367unsafe impl Sync for AdapterContext {}
368unsafe impl Send for AdapterContext {}
369
370impl AdapterContext {
371 pub fn is_owned(&self) -> bool {
372 self.egl.is_some()
373 }
374
375 pub fn egl_instance(&self) -> Option<&EglInstance> {
379 self.egl.as_ref().map(|egl| &*egl.instance)
380 }
381
382 pub fn raw_display(&self) -> Option<&khronos_egl::Display> {
386 self.egl.as_ref().map(|egl| &egl.display)
387 }
388
389 pub fn egl_version(&self) -> Option<(i32, i32)> {
393 self.egl.as_ref().map(|egl| egl.version)
394 }
395
396 pub fn raw_context(&self) -> *mut raw::c_void {
397 match self.egl {
398 Some(ref egl) => egl.raw.as_ptr(),
399 None => ptr::null_mut(),
400 }
401 }
402}
403
404struct EglContextLock<'a> {
405 instance: &'a Arc<EglInstance>,
406 display: khronos_egl::Display,
407}
408
409pub struct AdapterContextLock<'a> {
411 glow: MutexGuard<'a, glow::Context>,
412 egl: Option<EglContextLock<'a>>,
413}
414
415impl<'a> std::ops::Deref for AdapterContextLock<'a> {
416 type Target = glow::Context;
417
418 fn deref(&self) -> &Self::Target {
419 &self.glow
420 }
421}
422
423impl<'a> Drop for AdapterContextLock<'a> {
424 fn drop(&mut self) {
425 if let Some(egl) = self.egl.take() {
426 egl.instance
427 .make_current(egl.display, None, None, None)
428 .unwrap();
429 }
430 }
431}
432
433impl AdapterContext {
434 pub unsafe fn get_without_egl_lock(&self) -> MutexGuard<glow::Context> {
446 self.glow
447 .try_lock_for(Duration::from_secs(CONTEXT_LOCK_TIMEOUT_SECS))
448 .expect("Could not lock adapter context. This is most-likely a deadlock.")
449 }
450
451 #[track_caller]
454 pub fn lock<'a>(&'a self) -> AdapterContextLock<'a> {
455 let glow = self
456 .glow
457 .try_lock_for(Duration::from_secs(CONTEXT_LOCK_TIMEOUT_SECS))
460 .expect("Could not lock adapter context. This is most-likely a deadlock.");
461
462 let egl = self.egl.as_ref().map(|egl| {
463 egl.make_current();
464 EglContextLock {
465 instance: &egl.instance,
466 display: egl.display,
467 }
468 });
469
470 AdapterContextLock { glow, egl }
471 }
472}
473
474#[derive(Debug)]
475struct Inner {
476 egl: EglContext,
479 #[allow(unused)]
480 version: (i32, i32),
481 supports_native_window: bool,
482 config: khronos_egl::Config,
483 #[cfg_attr(target_os = "emscripten", allow(dead_code))]
484 wl_display: Option<*mut raw::c_void>,
485 srgb_kind: SrgbFrameBufferKind,
487}
488
489impl Inner {
490 fn create(
491 flags: crate::InstanceFlags,
492 egl: Arc<EglInstance>,
493 display: khronos_egl::Display,
494 ) -> Result<Self, crate::InstanceError> {
495 let version = egl.initialize(display).map_err(|_| crate::InstanceError)?;
496 let vendor = egl
497 .query_string(Some(display), khronos_egl::VENDOR)
498 .unwrap();
499 let display_extensions = egl
500 .query_string(Some(display), khronos_egl::EXTENSIONS)
501 .unwrap()
502 .to_string_lossy();
503 log::info!("Display vendor {:?}, version {:?}", vendor, version,);
504 log::debug!(
505 "Display extensions: {:#?}",
506 display_extensions.split_whitespace().collect::<Vec<_>>()
507 );
508
509 let srgb_kind = if version >= (1, 5) {
510 log::info!("\tEGL surface: +srgb");
511 SrgbFrameBufferKind::Core
512 } else if display_extensions.contains("EGL_KHR_gl_colorspace") {
513 log::info!("\tEGL surface: +srgb khr");
514 SrgbFrameBufferKind::Khr
515 } else {
516 log::warn!("\tEGL surface: -srgb");
517 SrgbFrameBufferKind::None
518 };
519
520 if log::max_level() >= log::LevelFilter::Trace {
521 log::trace!("Configurations:");
522 let config_count = egl.get_config_count(display).unwrap();
523 let mut configurations = Vec::with_capacity(config_count);
524 egl.get_configs(display, &mut configurations).unwrap();
525 for &config in configurations.iter() {
526 log::trace!("\tCONFORMANT=0x{:X}, RENDERABLE=0x{:X}, NATIVE_RENDERABLE=0x{:X}, SURFACE_TYPE=0x{:X}, ALPHA_SIZE={}",
527 egl.get_config_attrib(display, config, khronos_egl::CONFORMANT).unwrap(),
528 egl.get_config_attrib(display, config, khronos_egl::RENDERABLE_TYPE).unwrap(),
529 egl.get_config_attrib(display, config, khronos_egl::NATIVE_RENDERABLE).unwrap(),
530 egl.get_config_attrib(display, config, khronos_egl::SURFACE_TYPE).unwrap(),
531 egl.get_config_attrib(display, config, khronos_egl::ALPHA_SIZE).unwrap(),
532 );
533 }
534 }
535
536 let (config, supports_native_window) = choose_config(&egl, display, srgb_kind)?;
537 egl.bind_api(khronos_egl::OPENGL_ES_API).unwrap();
538
539 let needs_robustness = true;
540 let mut khr_context_flags = 0;
541 let supports_khr_context = display_extensions.contains("EGL_KHR_create_context");
542
543 let mut context_attributes = vec![
545 khronos_egl::CONTEXT_CLIENT_VERSION,
546 3, ];
548 if flags.contains(crate::InstanceFlags::DEBUG) {
549 if version >= (1, 5) {
550 log::info!("\tEGL context: +debug");
551 context_attributes.push(khronos_egl::CONTEXT_OPENGL_DEBUG);
552 context_attributes.push(khronos_egl::TRUE as _);
553 } else if supports_khr_context {
554 log::info!("\tEGL context: +debug KHR");
555 khr_context_flags |= EGL_CONTEXT_OPENGL_DEBUG_BIT_KHR;
556 } else {
557 log::info!("\tEGL context: -debug");
558 }
559 }
560 if needs_robustness {
561 if version >= (1, 5) && !display_extensions.contains("EGL_ANGLE_") {
565 log::info!("\tEGL context: +robust access");
566 context_attributes.push(khronos_egl::CONTEXT_OPENGL_ROBUST_ACCESS);
567 context_attributes.push(khronos_egl::TRUE as _);
568 } else if display_extensions.contains("EGL_EXT_create_context_robustness") {
569 log::info!("\tEGL context: +robust access EXT");
570 context_attributes.push(EGL_CONTEXT_OPENGL_ROBUST_ACCESS_EXT);
571 context_attributes.push(khronos_egl::TRUE as _);
572 } else {
573 log::warn!("\tEGL context: -robust access");
576 }
577
578 }
580 if khr_context_flags != 0 {
581 context_attributes.push(EGL_CONTEXT_FLAGS_KHR);
582 context_attributes.push(khr_context_flags);
583 }
584 context_attributes.push(khronos_egl::NONE);
585 let context = match egl.create_context(display, config, None, &context_attributes) {
586 Ok(context) => context,
587 Err(e) => {
588 log::warn!("unable to create GLES 3.x context: {:?}", e);
589 return Err(crate::InstanceError);
590 }
591 };
592
593 let pbuffer = if version >= (1, 5)
596 || display_extensions.contains("EGL_KHR_surfaceless_context")
597 || cfg!(target_os = "emscripten")
598 {
599 log::info!("\tEGL context: +surfaceless");
600 None
601 } else {
602 let attributes = [
603 khronos_egl::WIDTH,
604 1,
605 khronos_egl::HEIGHT,
606 1,
607 khronos_egl::NONE,
608 ];
609 egl.create_pbuffer_surface(display, config, &attributes)
610 .map(Some)
611 .map_err(|e| {
612 log::warn!("Error in create_pbuffer_surface: {:?}", e);
613 crate::InstanceError
614 })?
615 };
616
617 Ok(Self {
618 egl: EglContext {
619 instance: egl,
620 display,
621 raw: context,
622 pbuffer,
623 version,
624 },
625 version,
626 supports_native_window,
627 config,
628 wl_display: None,
629 srgb_kind,
630 })
631 }
632}
633
634impl Drop for Inner {
635 fn drop(&mut self) {
636 if let Err(e) = self
637 .egl
638 .instance
639 .destroy_context(self.egl.display, self.egl.raw)
640 {
641 log::warn!("Error in destroy_context: {:?}", e);
642 }
643 if let Err(e) = self.egl.instance.terminate(self.egl.display) {
644 log::warn!("Error in terminate: {:?}", e);
645 }
646 }
647}
648
649#[derive(Clone, Copy, Debug, PartialEq)]
650enum WindowKind {
651 Wayland,
652 X11,
653 AngleX11,
654 Unknown,
655}
656
657#[derive(Clone, Debug)]
658struct WindowSystemInterface {
659 display_owner: Option<Arc<DisplayOwner>>,
660 kind: WindowKind,
661}
662
663pub struct Instance {
664 wsi: WindowSystemInterface,
665 flags: crate::InstanceFlags,
666 inner: Mutex<Inner>,
667}
668
669impl Instance {
670 pub fn raw_display(&self) -> khronos_egl::Display {
671 self.inner
672 .try_lock()
673 .expect("Could not lock instance. This is most-likely a deadlock.")
674 .egl
675 .display
676 }
677
678 pub fn egl_version(&self) -> (i32, i32) {
680 self.inner
681 .try_lock()
682 .expect("Could not lock instance. This is most-likely a deadlock.")
683 .version
684 }
685
686 pub fn egl_config(&self) -> khronos_egl::Config {
687 self.inner
688 .try_lock()
689 .expect("Could not lock instance. This is most-likely a deadlock.")
690 .config
691 }
692}
693
694unsafe impl Send for Instance {}
695unsafe impl Sync for Instance {}
696
697impl crate::Instance<super::Api> for Instance {
698 unsafe fn init(desc: &crate::InstanceDescriptor) -> Result<Self, crate::InstanceError> {
699 #[cfg(target_os = "emscripten")]
700 let egl_result: Result<EglInstance, khronos_egl::Error> =
701 Ok(khronos_egl::Instance::new(khronos_egl::Static));
702
703 #[cfg(not(target_os = "emscripten"))]
704 let egl_result = if cfg!(windows) {
705 unsafe {
706 khronos_egl::DynamicInstance::<khronos_egl::EGL1_4>::load_required_from_filename(
707 "libEGL.dll",
708 )
709 }
710 } else if cfg!(any(target_os = "macos", target_os = "ios")) {
711 unsafe {
712 khronos_egl::DynamicInstance::<khronos_egl::EGL1_4>::load_required_from_filename(
713 "libEGL.dylib",
714 )
715 }
716 } else {
717 unsafe { khronos_egl::DynamicInstance::<khronos_egl::EGL1_4>::load_required() }
718 };
719 let egl = match egl_result {
720 Ok(egl) => Arc::new(egl),
721 Err(e) => {
722 log::info!("Unable to open libEGL: {:?}", e);
723 return Err(crate::InstanceError);
724 }
725 };
726
727 let client_extensions = egl.query_string(None, khronos_egl::EXTENSIONS);
728
729 let client_ext_str = match client_extensions {
730 Ok(ext) => ext.to_string_lossy().into_owned(),
731 Err(_) => String::new(),
732 };
733 log::debug!(
734 "Client extensions: {:#?}",
735 client_ext_str.split_whitespace().collect::<Vec<_>>()
736 );
737
738 let wayland_library = if client_ext_str.contains("EGL_EXT_platform_wayland") {
739 test_wayland_display()
740 } else {
741 None
742 };
743 let x11_display_library = if client_ext_str.contains("EGL_EXT_platform_x11") {
744 open_x_display()
745 } else {
746 None
747 };
748 let angle_x11_display_library = if client_ext_str.contains("EGL_ANGLE_platform_angle") {
749 open_x_display()
750 } else {
751 None
752 };
753
754 #[cfg(not(target_os = "emscripten"))]
755 let egl1_5 = egl.upcast::<khronos_egl::EGL1_5>();
756
757 #[cfg(target_os = "emscripten")]
758 let egl1_5: Option<&Arc<EglInstance>> = Some(&egl);
759
760 let (display, display_owner, wsi_kind) =
761 if let (Some(library), Some(egl)) = (wayland_library, egl1_5) {
762 log::info!("Using Wayland platform");
763 let display_attributes = [khronos_egl::ATTRIB_NONE];
764 let display = egl
765 .get_platform_display(
766 EGL_PLATFORM_WAYLAND_KHR,
767 khronos_egl::DEFAULT_DISPLAY,
768 &display_attributes,
769 )
770 .unwrap();
771 (display, Some(Arc::new(library)), WindowKind::Wayland)
772 } else if let (Some(display_owner), Some(egl)) = (x11_display_library, egl1_5) {
773 log::info!("Using X11 platform");
774 let display_attributes = [khronos_egl::ATTRIB_NONE];
775 let display = egl
776 .get_platform_display(
777 EGL_PLATFORM_X11_KHR,
778 display_owner.display.as_ptr(),
779 &display_attributes,
780 )
781 .unwrap();
782 (display, Some(Arc::new(display_owner)), WindowKind::X11)
783 } else if let (Some(display_owner), Some(egl)) = (angle_x11_display_library, egl1_5) {
784 log::info!("Using Angle platform with X11");
785 let display_attributes = [
786 EGL_PLATFORM_ANGLE_NATIVE_PLATFORM_TYPE_ANGLE as khronos_egl::Attrib,
787 EGL_PLATFORM_X11_KHR as khronos_egl::Attrib,
788 EGL_PLATFORM_ANGLE_DEBUG_LAYERS_ENABLED as khronos_egl::Attrib,
789 usize::from(desc.flags.contains(crate::InstanceFlags::VALIDATION)),
790 khronos_egl::ATTRIB_NONE,
791 ];
792 let display = egl
793 .get_platform_display(
794 EGL_PLATFORM_ANGLE_ANGLE,
795 display_owner.display.as_ptr(),
796 &display_attributes,
797 )
798 .unwrap();
799 (display, Some(Arc::new(display_owner)), WindowKind::AngleX11)
800 } else if client_ext_str.contains("EGL_MESA_platform_surfaceless") {
801 log::info!("No windowing system present. Using surfaceless platform");
802 let egl = egl1_5.expect("Failed to get EGL 1.5 for surfaceless");
803 let display = egl
804 .get_platform_display(
805 EGL_PLATFORM_SURFACELESS_MESA,
806 std::ptr::null_mut(),
807 &[khronos_egl::ATTRIB_NONE],
808 )
809 .unwrap();
810 (display, None, WindowKind::Unknown)
811 } else {
812 log::info!("EGL_MESA_platform_surfaceless not available. Using default platform");
813 let display = egl.get_display(khronos_egl::DEFAULT_DISPLAY).unwrap();
814 (display, None, WindowKind::Unknown)
815 };
816
817 if desc.flags.contains(crate::InstanceFlags::VALIDATION)
818 && client_ext_str.contains("EGL_KHR_debug")
819 {
820 log::info!("Enabling EGL debug output");
821 let function: EglDebugMessageControlFun = {
822 let addr = egl.get_proc_address("eglDebugMessageControlKHR").unwrap();
823 unsafe { std::mem::transmute(addr) }
824 };
825 let attributes = [
826 EGL_DEBUG_MSG_CRITICAL_KHR as khronos_egl::Attrib,
827 1,
828 EGL_DEBUG_MSG_ERROR_KHR as khronos_egl::Attrib,
829 1,
830 EGL_DEBUG_MSG_WARN_KHR as khronos_egl::Attrib,
831 1,
832 EGL_DEBUG_MSG_INFO_KHR as khronos_egl::Attrib,
833 1,
834 khronos_egl::ATTRIB_NONE,
835 ];
836 unsafe { (function)(Some(egl_debug_proc), attributes.as_ptr()) };
837 }
838
839 let inner = Inner::create(desc.flags, egl, display)?;
840
841 Ok(Instance {
842 wsi: WindowSystemInterface {
843 display_owner,
844 kind: wsi_kind,
845 },
846 flags: desc.flags,
847 inner: Mutex::new(inner),
848 })
849 }
850
851 #[cfg_attr(target_os = "macos", allow(unused, unused_mut, unreachable_code))]
852 unsafe fn create_surface(
853 &self,
854 display_handle: raw_window_handle::RawDisplayHandle,
855 window_handle: raw_window_handle::RawWindowHandle,
856 ) -> Result<Surface, crate::InstanceError> {
857 use raw_window_handle::RawWindowHandle as Rwh;
858
859 #[cfg_attr(
860 any(target_os = "android", target_os = "emscripten"),
861 allow(unused_mut)
862 )]
863 let mut inner = self.inner.lock();
864
865 match (window_handle, display_handle) {
866 (Rwh::Xlib(_), _) => {}
867 (Rwh::Xcb(_), _) => {}
868 (Rwh::Win32(_), _) => {}
869 (Rwh::AppKit(_), _) => {}
870 #[cfg(target_os = "android")]
871 (Rwh::AndroidNdk(handle), _) => {
872 let format = inner
873 .egl
874 .instance
875 .get_config_attrib(
876 inner.egl.display,
877 inner.config,
878 khronos_egl::NATIVE_VISUAL_ID,
879 )
880 .unwrap();
881
882 let ret = unsafe {
883 ANativeWindow_setBuffersGeometry(handle.a_native_window, 0, 0, format)
884 };
885
886 if ret != 0 {
887 log::error!("Error returned from ANativeWindow_setBuffersGeometry");
888 return Err(crate::InstanceError);
889 }
890 }
891 #[cfg(not(target_os = "emscripten"))]
892 (Rwh::Wayland(_), raw_window_handle::RawDisplayHandle::Wayland(display_handle)) => {
893 if inner
894 .wl_display
895 .map(|ptr| ptr != display_handle.display)
896 .unwrap_or(true)
897 {
898 log::warn!("Re-initializing Gles context due to Wayland window");
905
906 use std::ops::DerefMut;
907 let display_attributes = [khronos_egl::ATTRIB_NONE];
908
909 let display = inner
910 .egl
911 .instance
912 .upcast::<khronos_egl::EGL1_5>()
913 .unwrap()
914 .get_platform_display(
915 EGL_PLATFORM_WAYLAND_KHR,
916 display_handle.display,
917 &display_attributes,
918 )
919 .unwrap();
920
921 let new_inner =
922 Inner::create(self.flags, Arc::clone(&inner.egl.instance), display)
923 .map_err(|_| crate::InstanceError)?;
924
925 let old_inner = std::mem::replace(inner.deref_mut(), new_inner);
926 inner.wl_display = Some(display_handle.display);
927
928 drop(old_inner);
929 }
930 }
931 #[cfg(target_os = "emscripten")]
932 (Rwh::Web(_), _) => {}
933 other => {
934 log::error!("Unsupported window: {:?}", other);
935 return Err(crate::InstanceError);
936 }
937 };
938
939 inner.egl.unmake_current();
940
941 Ok(Surface {
942 egl: inner.egl.clone(),
943 wsi: self.wsi.clone(),
944 config: inner.config,
945 presentable: inner.supports_native_window,
946 raw_window_handle: window_handle,
947 swapchain: None,
948 srgb_kind: inner.srgb_kind,
949 })
950 }
951 unsafe fn destroy_surface(&self, _surface: Surface) {}
952
953 unsafe fn enumerate_adapters(&self) -> Vec<crate::ExposedAdapter<super::Api>> {
954 let inner = self.inner.lock();
955 inner.egl.make_current();
956
957 let gl = unsafe {
958 glow::Context::from_loader_function(|name| {
959 inner
960 .egl
961 .instance
962 .get_proc_address(name)
963 .map_or(ptr::null(), |p| p as *const _)
964 })
965 };
966
967 if self.flags.contains(crate::InstanceFlags::DEBUG) && gl.supports_debug() {
968 log::info!("Max label length: {}", unsafe {
969 gl.get_parameter_i32(glow::MAX_LABEL_LENGTH)
970 });
971 }
972
973 if self.flags.contains(crate::InstanceFlags::VALIDATION) && gl.supports_debug() {
974 log::info!("Enabling GLES debug output");
975 unsafe { gl.enable(glow::DEBUG_OUTPUT) };
976 unsafe { gl.debug_message_callback(gl_debug_message_callback) };
977 }
978
979 inner.egl.unmake_current();
980
981 unsafe {
982 super::Adapter::expose(AdapterContext {
983 glow: Mutex::new(gl),
984 egl: Some(inner.egl.clone()),
985 })
986 }
987 .into_iter()
988 .collect()
989 }
990}
991
992impl super::Adapter {
993 pub unsafe fn new_external(
1001 fun: impl FnMut(&str) -> *const ffi::c_void,
1002 ) -> Option<crate::ExposedAdapter<super::Api>> {
1003 let context = unsafe { glow::Context::from_loader_function(fun) };
1004 unsafe {
1005 Self::expose(AdapterContext {
1006 glow: Mutex::new(context),
1007 egl: None,
1008 })
1009 }
1010 }
1011
1012 pub fn adapter_context(&self) -> &AdapterContext {
1013 &self.shared.context
1014 }
1015}
1016
1017impl super::Device {
1018 pub fn context(&self) -> &AdapterContext {
1020 &self.shared.context
1021 }
1022}
1023
1024#[derive(Debug)]
1025pub struct Swapchain {
1026 surface: khronos_egl::Surface,
1027 wl_window: Option<*mut raw::c_void>,
1028 framebuffer: glow::Framebuffer,
1029 renderbuffer: glow::Renderbuffer,
1030 extent: wgt::Extent3d,
1032 format: wgt::TextureFormat,
1033 format_desc: super::TextureFormatDesc,
1034 #[allow(unused)]
1035 sample_type: wgt::TextureSampleType,
1036}
1037
1038#[derive(Debug)]
1039pub struct Surface {
1040 egl: EglContext,
1041 wsi: WindowSystemInterface,
1042 config: khronos_egl::Config,
1043 pub(super) presentable: bool,
1044 raw_window_handle: raw_window_handle::RawWindowHandle,
1045 swapchain: Option<Swapchain>,
1046 srgb_kind: SrgbFrameBufferKind,
1047}
1048
1049unsafe impl Send for Surface {}
1050unsafe impl Sync for Surface {}
1051
1052impl Surface {
1053 pub(super) unsafe fn present(
1054 &mut self,
1055 _suf_texture: super::Texture,
1056 gl: &glow::Context,
1057 ) -> Result<(), crate::SurfaceError> {
1058 let sc = self.swapchain.as_ref().unwrap();
1059
1060 self.egl
1061 .instance
1062 .make_current(
1063 self.egl.display,
1064 Some(sc.surface),
1065 Some(sc.surface),
1066 Some(self.egl.raw),
1067 )
1068 .map_err(|e| {
1069 log::error!("make_current(surface) failed: {}", e);
1070 crate::SurfaceError::Lost
1071 })?;
1072
1073 unsafe { gl.disable(glow::SCISSOR_TEST) };
1074 unsafe { gl.color_mask(true, true, true, true) };
1075
1076 unsafe { gl.bind_framebuffer(glow::DRAW_FRAMEBUFFER, None) };
1077 unsafe { gl.bind_framebuffer(glow::READ_FRAMEBUFFER, Some(sc.framebuffer)) };
1078 unsafe {
1082 gl.blit_framebuffer(
1083 0,
1084 sc.extent.height as i32,
1085 sc.extent.width as i32,
1086 0,
1087 0,
1088 0,
1089 sc.extent.width as i32,
1090 sc.extent.height as i32,
1091 glow::COLOR_BUFFER_BIT,
1092 glow::NEAREST,
1093 )
1094 };
1095 unsafe { gl.bind_framebuffer(glow::READ_FRAMEBUFFER, None) };
1096
1097 self.egl
1098 .instance
1099 .swap_buffers(self.egl.display, sc.surface)
1100 .map_err(|e| {
1101 log::error!("swap_buffers failed: {}", e);
1102 crate::SurfaceError::Lost
1103 })?;
1104 self.egl
1105 .instance
1106 .make_current(self.egl.display, None, None, None)
1107 .map_err(|e| {
1108 log::error!("make_current(null) failed: {}", e);
1109 crate::SurfaceError::Lost
1110 })?;
1111
1112 Ok(())
1113 }
1114
1115 unsafe fn unconfigure_impl(
1116 &mut self,
1117 device: &super::Device,
1118 ) -> Option<(khronos_egl::Surface, Option<*mut raw::c_void>)> {
1119 let gl = &device.shared.context.lock();
1120 match self.swapchain.take() {
1121 Some(sc) => {
1122 unsafe { gl.delete_renderbuffer(sc.renderbuffer) };
1123 unsafe { gl.delete_framebuffer(sc.framebuffer) };
1124 Some((sc.surface, sc.wl_window))
1125 }
1126 None => None,
1127 }
1128 }
1129
1130 pub fn supports_srgb(&self) -> bool {
1131 match self.srgb_kind {
1132 SrgbFrameBufferKind::None => false,
1133 _ => true,
1134 }
1135 }
1136}
1137
1138impl crate::Surface<super::Api> for Surface {
1139 unsafe fn configure(
1140 &mut self,
1141 device: &super::Device,
1142 config: &crate::SurfaceConfiguration,
1143 ) -> Result<(), crate::SurfaceError> {
1144 use raw_window_handle::RawWindowHandle as Rwh;
1145
1146 let (surface, wl_window) = match unsafe { self.unconfigure_impl(device) } {
1147 Some(pair) => pair,
1148 None => {
1149 let mut wl_window = None;
1150 let (mut temp_xlib_handle, mut temp_xcb_handle);
1151 #[allow(trivial_casts)]
1152 let native_window_ptr = match (self.wsi.kind, self.raw_window_handle) {
1153 (WindowKind::Unknown | WindowKind::X11, Rwh::Xlib(handle)) => {
1154 temp_xlib_handle = handle.window;
1155 &mut temp_xlib_handle as *mut _ as *mut std::ffi::c_void
1156 }
1157 (WindowKind::AngleX11, Rwh::Xlib(handle)) => {
1158 handle.window as *mut std::ffi::c_void
1159 }
1160 (WindowKind::Unknown | WindowKind::X11, Rwh::Xcb(handle)) => {
1161 temp_xcb_handle = handle.window;
1162 &mut temp_xcb_handle as *mut _ as *mut std::ffi::c_void
1163 }
1164 (WindowKind::AngleX11, Rwh::Xcb(handle)) => {
1165 handle.window as *mut std::ffi::c_void
1166 }
1167 (WindowKind::Unknown, Rwh::AndroidNdk(handle)) => handle.a_native_window,
1168 (WindowKind::Wayland, Rwh::Wayland(handle)) => {
1169 let library = &self.wsi.display_owner.as_ref().unwrap().library;
1170 let wl_egl_window_create: libloading::Symbol<WlEglWindowCreateFun> =
1171 unsafe { library.get(b"wl_egl_window_create") }.unwrap();
1172 let window = unsafe { wl_egl_window_create(handle.surface, 640, 480) }
1173 as *mut _ as *mut std::ffi::c_void;
1174 wl_window = Some(window);
1175 window
1176 }
1177 #[cfg(target_os = "emscripten")]
1178 (WindowKind::Unknown, Rwh::Web(handle)) => handle.id as *mut std::ffi::c_void,
1179 (WindowKind::Unknown, Rwh::Win32(handle)) => handle.hwnd,
1180 (WindowKind::Unknown, Rwh::AppKit(handle)) => {
1181 #[cfg(not(target_os = "macos"))]
1182 let window_ptr = handle.ns_view;
1183 #[cfg(target_os = "macos")]
1184 let window_ptr = {
1185 use objc::{msg_send, runtime::Object, sel, sel_impl};
1186 let layer: *mut Object =
1188 msg_send![handle.ns_view as *mut Object, layer];
1189 layer as *mut ffi::c_void
1190 };
1191 window_ptr
1192 }
1193 _ => {
1194 log::warn!(
1195 "Initialized platform {:?} doesn't work with window {:?}",
1196 self.wsi.kind,
1197 self.raw_window_handle
1198 );
1199 return Err(crate::SurfaceError::Other("incompatible window kind"));
1200 }
1201 };
1202
1203 let mut attributes = vec![
1204 khronos_egl::RENDER_BUFFER,
1205 if cfg!(any(target_os = "android", target_os = "macos"))
1209 || cfg!(windows)
1210 || self.wsi.kind == WindowKind::AngleX11
1211 {
1212 khronos_egl::BACK_BUFFER
1213 } else {
1214 khronos_egl::SINGLE_BUFFER
1215 },
1216 ];
1217 if config.format.is_srgb() {
1218 match self.srgb_kind {
1219 SrgbFrameBufferKind::None => {}
1220 SrgbFrameBufferKind::Core => {
1221 attributes.push(khronos_egl::GL_COLORSPACE);
1222 attributes.push(khronos_egl::GL_COLORSPACE_SRGB);
1223 }
1224 SrgbFrameBufferKind::Khr => {
1225 attributes.push(EGL_GL_COLORSPACE_KHR as i32);
1226 attributes.push(EGL_GL_COLORSPACE_SRGB_KHR as i32);
1227 }
1228 }
1229 }
1230 attributes.push(khronos_egl::ATTRIB_NONE as i32);
1231
1232 #[cfg(not(target_os = "emscripten"))]
1233 let egl1_5 = self.egl.instance.upcast::<khronos_egl::EGL1_5>();
1234
1235 #[cfg(target_os = "emscripten")]
1236 let egl1_5: Option<&Arc<EglInstance>> = Some(&self.egl.instance);
1237
1238 let raw_result = match egl1_5 {
1240 Some(egl) if self.wsi.kind != WindowKind::Unknown => {
1241 let attributes_usize = attributes
1242 .into_iter()
1243 .map(|v| v as usize)
1244 .collect::<Vec<_>>();
1245 egl.create_platform_window_surface(
1246 self.egl.display,
1247 self.config,
1248 native_window_ptr,
1249 &attributes_usize,
1250 )
1251 }
1252 _ => unsafe {
1253 self.egl.instance.create_window_surface(
1254 self.egl.display,
1255 self.config,
1256 native_window_ptr,
1257 Some(&attributes),
1258 )
1259 },
1260 };
1261
1262 match raw_result {
1263 Ok(raw) => (raw, wl_window),
1264 Err(e) => {
1265 log::warn!("Error in create_window_surface: {:?}", e);
1266 return Err(crate::SurfaceError::Lost);
1267 }
1268 }
1269 }
1270 };
1271
1272 if let Some(window) = wl_window {
1273 let library = &self.wsi.display_owner.as_ref().unwrap().library;
1274 let wl_egl_window_resize: libloading::Symbol<WlEglWindowResizeFun> =
1275 unsafe { library.get(b"wl_egl_window_resize") }.unwrap();
1276 unsafe {
1277 wl_egl_window_resize(
1278 window,
1279 config.extent.width as i32,
1280 config.extent.height as i32,
1281 0,
1282 0,
1283 )
1284 };
1285 }
1286
1287 let format_desc = device.shared.describe_texture_format(config.format);
1288 let gl = &device.shared.context.lock();
1289 let renderbuffer = unsafe { gl.create_renderbuffer() }.map_err(|error| {
1290 log::error!("Internal swapchain renderbuffer creation failed: {error}");
1291 crate::DeviceError::OutOfMemory
1292 })?;
1293 unsafe { gl.bind_renderbuffer(glow::RENDERBUFFER, Some(renderbuffer)) };
1294 unsafe {
1295 gl.renderbuffer_storage(
1296 glow::RENDERBUFFER,
1297 format_desc.internal,
1298 config.extent.width as _,
1299 config.extent.height as _,
1300 )
1301 };
1302 let framebuffer = unsafe { gl.create_framebuffer() }.map_err(|error| {
1303 log::error!("Internal swapchain framebuffer creation failed: {error}");
1304 crate::DeviceError::OutOfMemory
1305 })?;
1306 unsafe { gl.bind_framebuffer(glow::READ_FRAMEBUFFER, Some(framebuffer)) };
1307 unsafe {
1308 gl.framebuffer_renderbuffer(
1309 glow::READ_FRAMEBUFFER,
1310 glow::COLOR_ATTACHMENT0,
1311 glow::RENDERBUFFER,
1312 Some(renderbuffer),
1313 )
1314 };
1315 unsafe { gl.bind_renderbuffer(glow::RENDERBUFFER, None) };
1316 unsafe { gl.bind_framebuffer(glow::READ_FRAMEBUFFER, None) };
1317
1318 self.swapchain = Some(Swapchain {
1319 surface,
1320 wl_window,
1321 renderbuffer,
1322 framebuffer,
1323 extent: config.extent,
1324 format: config.format,
1325 format_desc,
1326 sample_type: wgt::TextureSampleType::Float { filterable: false },
1327 });
1328
1329 Ok(())
1330 }
1331
1332 unsafe fn unconfigure(&mut self, device: &super::Device) {
1333 if let Some((surface, wl_window)) = unsafe { self.unconfigure_impl(device) } {
1334 self.egl
1335 .instance
1336 .destroy_surface(self.egl.display, surface)
1337 .unwrap();
1338 if let Some(window) = wl_window {
1339 let library = &self
1340 .wsi
1341 .display_owner
1342 .as_ref()
1343 .expect("unsupported window")
1344 .library;
1345 let wl_egl_window_destroy: libloading::Symbol<WlEglWindowDestroyFun> =
1346 unsafe { library.get(b"wl_egl_window_destroy") }.unwrap();
1347 unsafe { wl_egl_window_destroy(window) };
1348 }
1349 }
1350 }
1351
1352 unsafe fn acquire_texture(
1353 &mut self,
1354 _timeout_ms: Option<Duration>, ) -> Result<Option<crate::AcquiredSurfaceTexture<super::Api>>, crate::SurfaceError> {
1356 let sc = self.swapchain.as_ref().unwrap();
1357 let texture = super::Texture {
1358 inner: super::TextureInner::Renderbuffer {
1359 raw: sc.renderbuffer,
1360 },
1361 drop_guard: None,
1362 array_layer_count: 1,
1363 mip_level_count: 1,
1364 format: sc.format,
1365 format_desc: sc.format_desc.clone(),
1366 copy_size: crate::CopyExtent {
1367 width: sc.extent.width,
1368 height: sc.extent.height,
1369 depth: 1,
1370 },
1371 is_cubemap: false,
1372 };
1373 Ok(Some(crate::AcquiredSurfaceTexture {
1374 texture,
1375 suboptimal: false,
1376 }))
1377 }
1378 unsafe fn discard_texture(&mut self, _texture: super::Texture) {}
1379}