1use crate::{conv, native, GlContainer, PhysicalDevice, Starc};
4use glow::HasContext;
5use hal::{image, window as w};
6use parking_lot::Mutex;
7use std::{os::raw, ptr};
8
9#[derive(Debug)]
10pub struct Swapchain {
11 framebuffer: glow::Framebuffer,
12 renderbuffer: glow::Renderbuffer,
13 extent: w::Extent2D,
15 format: native::TextureFormat,
16 channel: hal::format::ChannelType,
17}
18
19#[derive(Debug)]
20pub struct Instance {
21 wsi_library: Option<libloading::Library>,
22 inner: Mutex<Inner>,
23}
24
25#[derive(Debug)]
26pub struct Inner {
27 egl: Starc<egl::DynamicInstance<egl::EGL1_4>>,
28 version: (i32, i32),
29 supports_native_window: bool,
30 display: egl::Display,
31 config: egl::Config,
32 context: egl::Context,
33 pbuffer: Option<egl::Surface>,
36 wl_display: Option<*mut raw::c_void>,
37}
38
39unsafe impl Send for Instance {}
40unsafe impl Sync for Instance {}
41
42const EGL_PLATFORM_WAYLAND_KHR: u32 = 0x31D8;
43const EGL_PLATFORM_X11_KHR: u32 = 0x31D5;
44
45type XOpenDisplayFun =
46 unsafe extern "system" fn(display_name: *const raw::c_char) -> *mut raw::c_void;
47
48type WlDisplayConnectFun =
49 unsafe extern "system" fn(display_name: *const raw::c_char) -> *mut raw::c_void;
50
51type WlDisplayDisconnectFun = unsafe extern "system" fn(display: *const raw::c_void);
52
53#[cfg(not(any(target_os = "android", target_os = "macos")))]
54type WlEglWindowCreateFun = unsafe extern "system" fn(
55 surface: *const raw::c_void,
56 width: raw::c_int,
57 height: raw::c_int,
58) -> *mut raw::c_void;
59
60type WlEglWindowResizeFun = unsafe extern "system" fn(
61 window: *const raw::c_void,
62 width: raw::c_int,
63 height: raw::c_int,
64 dx: raw::c_int,
65 dy: raw::c_int,
66);
67
68type WlEglWindowDestroyFun = unsafe extern "system" fn(window: *const raw::c_void);
69
70#[cfg(target_os = "android")]
71extern "C" {
72 pub fn ANativeWindow_setBuffersGeometry(
73 window: *mut raw::c_void,
74 width: i32,
75 height: i32,
76 format: i32,
77 ) -> i32;
78}
79
80fn open_x_display() -> Option<(ptr::NonNull<raw::c_void>, libloading::Library)> {
81 log::info!("Loading X11 library to get the current display");
82 unsafe {
83 let library = libloading::Library::new("libX11.so").ok()?;
84 let func: libloading::Symbol<XOpenDisplayFun> = library.get(b"XOpenDisplay").unwrap();
85 let result = func(ptr::null());
86 ptr::NonNull::new(result).map(|ptr| (ptr, library))
87 }
88}
89
90fn test_wayland_display() -> Option<libloading::Library> {
91 log::info!("Loading Wayland library to get the current display");
95 let library = unsafe {
96 let client_library = libloading::Library::new("libwayland-client.so").ok()?;
97 let wl_display_connect: libloading::Symbol<WlDisplayConnectFun> =
98 client_library.get(b"wl_display_connect").unwrap();
99 let wl_display_disconnect: libloading::Symbol<WlDisplayDisconnectFun> =
100 client_library.get(b"wl_display_disconnect").unwrap();
101 let display = ptr::NonNull::new(wl_display_connect(ptr::null()))?;
102 wl_display_disconnect(display.as_ptr());
103 libloading::Library::new("libwayland-egl.so").ok()?
104 };
105 Some(library)
106}
107
108fn choose_config(
110 egl: &egl::DynamicInstance<egl::EGL1_4>,
111 display: egl::Display,
112) -> Result<(egl::Config, bool), hal::UnsupportedBackend> {
113 let tiers = [
115 (
116 "off-screen",
117 &[egl::RENDERABLE_TYPE, egl::OPENGL_ES2_BIT][..],
118 ),
119 ("presentation", &[egl::SURFACE_TYPE, egl::WINDOW_BIT]),
120 #[cfg(not(target_os = "android"))]
121 ("native-render", &[egl::NATIVE_RENDERABLE, egl::TRUE as _]),
122 ];
123
124 let mut attributes = Vec::with_capacity(7);
125 for tier_max in (0..tiers.len()).rev() {
126 let name = tiers[tier_max].0;
127 log::info!("Trying {}", name);
128
129 attributes.clear();
130 for &(_, tier_attr) in tiers[..=tier_max].iter() {
131 attributes.extend_from_slice(tier_attr);
132 }
133 attributes.push(egl::NONE);
134
135 match egl.choose_first_config(display, &attributes) {
136 Ok(Some(config)) => {
137 return Ok((config, tier_max >= 1));
138 }
139 Ok(None) => {
140 log::warn!("No config found!");
141 }
142 Err(e) => {
143 log::error!("error in choose_first_config: {:?}", e);
144 }
145 }
146 }
147
148 Err(hal::UnsupportedBackend)
149}
150
151impl Inner {
152 fn create(
153 egl: Starc<egl::DynamicInstance<egl::EGL1_4>>,
154 display: egl::Display,
155 wsi_library: Option<&libloading::Library>,
156 ) -> Result<Self, hal::UnsupportedBackend> {
157 let version = egl
158 .initialize(display)
159 .map_err(|_| hal::UnsupportedBackend)?;
160 let vendor = egl.query_string(Some(display), egl::VENDOR).unwrap();
161 let display_extensions = egl
162 .query_string(Some(display), egl::EXTENSIONS)
163 .unwrap()
164 .to_string_lossy();
165 log::info!(
166 "Display vendor {:?}, version {:?}, extensions: {:?}",
167 vendor,
168 version,
169 display_extensions
170 );
171
172 if log::max_level() >= log::LevelFilter::Trace {
173 log::trace!("Configurations:");
174 let config_count = egl.get_config_count(display).unwrap();
175 let mut configurations = Vec::with_capacity(config_count);
176 egl.get_configs(display, &mut configurations).unwrap();
177 for &config in configurations.iter() {
178 log::trace!("\tCONFORMANT=0x{:X}, RENDERABLE=0x{:X}, NATIVE_RENDERABLE=0x{:X}, SURFACE_TYPE=0x{:X}",
179 egl.get_config_attrib(display, config, egl::CONFORMANT).unwrap(),
180 egl.get_config_attrib(display, config, egl::RENDERABLE_TYPE).unwrap(),
181 egl.get_config_attrib(display, config, egl::NATIVE_RENDERABLE).unwrap(),
182 egl.get_config_attrib(display, config, egl::SURFACE_TYPE).unwrap(),
183 );
184 }
185 }
186
187 let (config, supports_native_window) = choose_config(&egl, display)?;
188 egl.bind_api(egl::OPENGL_ES_API).unwrap();
189
190 let mut context_attributes = vec![
192 egl::CONTEXT_CLIENT_VERSION,
193 3, ];
195 if cfg!(debug_assertions) && wsi_library.is_none() && !cfg!(target_os = "android") {
196 context_attributes.push(egl::CONTEXT_OPENGL_DEBUG);
198 context_attributes.push(egl::TRUE as _);
199 }
200 context_attributes.push(egl::NONE as _);
201 let context = match egl.create_context(display, config, None, &context_attributes) {
202 Ok(context) => context,
203 Err(e) => {
204 log::warn!("unable to create GLES 3.x context: {:?}", e);
205 return Err(hal::UnsupportedBackend);
206 }
207 };
208
209 let pbuffer = if version < (1, 5)
212 || !display_extensions.contains("EGL_KHR_surfaceless_context")
213 {
214 let attributes = [egl::WIDTH, 1, egl::HEIGHT, 1, egl::NONE];
215 egl.create_pbuffer_surface(display, config, &attributes)
216 .map(Some)
217 .map_err(|e| {
218 log::warn!("Error in create_pbuffer_surface: {:?}", e);
219 hal::UnsupportedBackend
220 })?
221 } else {
222 log::info!("EGL_KHR_surfaceless_context is present. No need to create a dummy pbuffer");
223 None
224 };
225
226 Ok(Self {
227 egl,
228 display,
229 version,
230 supports_native_window,
231 config,
232 context,
233 pbuffer,
234 wl_display: None,
235 })
236 }
237}
238
239impl Drop for Inner {
240 fn drop(&mut self) {
241 if let Err(e) = self.egl.destroy_context(self.display, self.context) {
242 log::warn!("Error in destroy_context: {:?}", e);
243 }
244 if let Err(e) = self.egl.terminate(self.display) {
245 log::warn!("Error in terminate: {:?}", e);
246 }
247 }
248}
249
250impl hal::Instance<crate::Backend> for Instance {
251 fn create(_: &str, _: u32) -> Result<Self, hal::UnsupportedBackend> {
252 let egl = match unsafe { egl::DynamicInstance::<egl::EGL1_4>::load_required() } {
253 Ok(egl) => Starc::new(egl),
254 Err(e) => {
255 log::warn!("Unable to open libEGL.so: {:?}", e);
256 return Err(hal::UnsupportedBackend);
257 }
258 };
259
260 let client_extensions = egl.query_string(None, egl::EXTENSIONS);
261
262 let client_ext_str = match client_extensions {
263 Ok(ext) => ext.to_string_lossy().into_owned(),
264 Err(_) => String::new(),
265 };
266 log::info!("Client extensions: {:?}", client_ext_str);
267
268 let mut wsi_library = None;
269
270 let wayland_library = if client_ext_str.contains(&"EGL_EXT_platform_wayland") {
271 test_wayland_display()
272 } else {
273 None
274 };
275
276 let x11_display_library = if client_ext_str.contains(&"EGL_EXT_platform_x11") {
277 open_x_display()
278 } else {
279 None
280 };
281
282 let display = if let (Some(library), Some(egl)) =
283 (wayland_library, egl.upcast::<egl::EGL1_5>())
284 {
285 log::info!("Using Wayland platform");
286 let display_attributes = [egl::ATTRIB_NONE];
287 wsi_library = Some(library);
288 egl.get_platform_display(
289 EGL_PLATFORM_WAYLAND_KHR,
290 egl::DEFAULT_DISPLAY,
291 &display_attributes,
292 )
293 .unwrap()
294 } else if let (Some((display, library)), Some(egl)) =
295 (x11_display_library, egl.upcast::<egl::EGL1_5>())
296 {
297 log::info!("Using X11 platform");
298 let display_attributes = [egl::ATTRIB_NONE];
299 wsi_library = Some(library);
300 egl.get_platform_display(EGL_PLATFORM_X11_KHR, display.as_ptr(), &display_attributes)
301 .unwrap()
302 } else {
303 log::info!("Using default platform");
304 egl.get_display(egl::DEFAULT_DISPLAY).unwrap()
305 };
306
307 let inner = Inner::create(egl.clone(), display, wsi_library.as_ref())?;
308
309 Ok(Instance {
310 inner: Mutex::new(inner),
311 wsi_library,
312 })
313 }
314
315 fn enumerate_adapters(&self) -> Vec<hal::adapter::Adapter<crate::Backend>> {
316 let inner = self.inner.lock();
317 inner
318 .egl
319 .make_current(
320 inner.display,
321 inner.pbuffer,
322 inner.pbuffer,
323 Some(inner.context),
324 )
325 .unwrap();
326
327 let context = unsafe {
328 glow::Context::from_loader_function(|name| {
329 inner
330 .egl
331 .get_proc_address(name)
332 .map_or(ptr::null(), |p| p as *const _)
333 })
334 };
335 vec![PhysicalDevice::new_adapter(context)]
337 }
338
339 #[cfg_attr(target_os = "macos", allow(unused, unused_mut, unreachable_code))]
340 unsafe fn create_surface(
341 &self,
342 has_handle: &impl raw_window_handle::HasRawWindowHandle,
343 ) -> Result<Surface, w::InitError> {
344 use raw_window_handle::RawWindowHandle as Rwh;
345
346 let mut inner = self.inner.lock();
347 let mut wl_window = None;
348 #[cfg(not(any(target_os = "android", target_os = "macos")))]
349 let (mut temp_xlib_handle, mut temp_xcb_handle);
350 let native_window_ptr = match has_handle.raw_window_handle() {
351 #[cfg(not(any(target_os = "android", target_os = "macos")))]
352 Rwh::Xlib(handle) => {
353 temp_xlib_handle = handle.window;
354 &mut temp_xlib_handle as *mut _ as *mut std::ffi::c_void
355 }
356 #[cfg(not(any(target_os = "android", target_os = "macos")))]
357 Rwh::Xcb(handle) => {
358 temp_xcb_handle = handle.window;
359 &mut temp_xcb_handle as *mut _ as *mut std::ffi::c_void
360 }
361 #[cfg(target_os = "android")]
362 Rwh::Android(handle) => handle.a_native_window as *mut _ as *mut std::ffi::c_void,
363 #[cfg(not(any(target_os = "android", target_os = "macos")))]
364 Rwh::Wayland(handle) => {
365 if inner
372 .wl_display
373 .map(|ptr| ptr != handle.display)
374 .unwrap_or(true)
375 {
376 use std::ops::DerefMut;
377 let display_attributes = [egl::ATTRIB_NONE];
378 let display = inner
379 .egl
380 .upcast::<egl::EGL1_5>()
381 .unwrap()
382 .get_platform_display(
383 EGL_PLATFORM_WAYLAND_KHR,
384 handle.display,
385 &display_attributes,
386 )
387 .unwrap();
388
389 let new_inner =
390 Inner::create(inner.egl.clone(), display, self.wsi_library.as_ref())
391 .map_err(|_| w::InitError::UnsupportedWindowHandle)?;
392
393 let old_inner = std::mem::replace(inner.deref_mut(), new_inner);
394 inner.wl_display = Some(handle.display);
395 drop(old_inner);
396 }
397
398 let wl_egl_window_create: libloading::Symbol<WlEglWindowCreateFun> = self
399 .wsi_library
400 .as_ref()
401 .expect("unsupported window")
402 .get(b"wl_egl_window_create")
403 .unwrap();
404 let result = wl_egl_window_create(handle.surface, 640, 480) as *mut _
405 as *mut std::ffi::c_void;
406 wl_window = Some(result);
407 result
408 }
409 other => {
410 log::error!("Unsupported window: {:?}", other);
411 return Err(w::InitError::UnsupportedWindowHandle);
412 }
413 };
414
415 let mut attributes = vec![
416 egl::RENDER_BUFFER as usize,
417 if cfg!(target_os = "android") {
418 egl::BACK_BUFFER as usize
419 } else {
420 egl::SINGLE_BUFFER as usize
421 },
422 ];
423 if inner.version >= (1, 5) {
424 attributes.push(egl::GL_COLORSPACE as usize);
426 attributes.push(egl::GL_COLORSPACE_SRGB as usize);
427 }
428 attributes.push(egl::ATTRIB_NONE);
429
430 let raw = if let Some(egl) = inner.egl.upcast::<egl::EGL1_5>() {
431 egl.create_platform_window_surface(
432 inner.display,
433 inner.config,
434 native_window_ptr,
435 &attributes,
436 )
437 .map_err(|e| {
438 log::warn!("Error in create_platform_window_surface: {:?}", e);
439 w::InitError::UnsupportedWindowHandle
440 })
441 } else {
442 let attributes_i32: Vec<i32> = attributes.iter().map(|a| (*a as i32).into()).collect();
443 inner
444 .egl
445 .create_window_surface(
446 inner.display,
447 inner.config,
448 native_window_ptr,
449 Some(&attributes_i32),
450 )
451 .map_err(|e| {
452 log::warn!("Error in create_platform_window_surface: {:?}", e);
453 w::InitError::UnsupportedWindowHandle
454 })
455 }?;
456
457 #[cfg(target_os = "android")]
458 {
459 let format = inner
460 .egl
461 .get_config_attrib(inner.display, inner.config, egl::NATIVE_VISUAL_ID)
462 .unwrap();
463
464 let ret = ANativeWindow_setBuffersGeometry(native_window_ptr, 0, 0, format);
465
466 if ret != 0 {
467 log::error!("Error returned from ANativeWindow_setBuffersGeometry");
468 return Err(w::InitError::UnsupportedWindowHandle);
469 }
470 }
471
472 Ok(Surface {
473 egl: inner.egl.clone(),
474 raw,
475 display: inner.display,
476 context: inner.context,
477 presentable: inner.supports_native_window,
478 pbuffer: inner.pbuffer,
479 wl_window,
480 swapchain: None,
481 })
482 }
483
484 unsafe fn destroy_surface(&self, surface: Surface) {
485 let inner = self.inner.lock();
486 inner
487 .egl
488 .destroy_surface(inner.display, surface.raw)
489 .unwrap();
490 if let Some(wl_window) = surface.wl_window {
491 let wl_egl_window_destroy: libloading::Symbol<WlEglWindowDestroyFun> = self
492 .wsi_library
493 .as_ref()
494 .expect("unsupported window")
495 .get(b"wl_egl_window_destroy")
496 .unwrap();
497 wl_egl_window_destroy(wl_window)
498 }
499 }
500
501 unsafe fn create_display_plane_surface(
502 &self,
503 _display_plane: &hal::display::DisplayPlane<crate::Backend>,
504 _plane_stack_index: u32,
505 _transformation: hal::display::SurfaceTransform,
506 _alpha: hal::display::DisplayPlaneAlpha,
507 _image_extent: hal::window::Extent2D,
508 ) -> Result<Surface, hal::display::DisplayPlaneSurfaceError> {
509 unimplemented!();
510 }
511}
512
513#[derive(Debug)]
514pub struct Surface {
515 egl: Starc<egl::DynamicInstance<egl::EGL1_4>>,
516 raw: egl::Surface,
517 display: egl::Display,
518 context: egl::Context,
519 pbuffer: Option<egl::Surface>,
520 presentable: bool,
521 wl_window: Option<*mut raw::c_void>,
522 pub(crate) swapchain: Option<Swapchain>,
523}
524
525unsafe impl Send for Surface {}
526unsafe impl Sync for Surface {}
527
528impl w::PresentationSurface<crate::Backend> for Surface {
529 type SwapchainImage = native::SwapchainImage;
530
531 unsafe fn configure_swapchain(
532 &mut self,
533 device: &crate::Device,
534 config: w::SwapchainConfig,
535 ) -> Result<(), w::SwapchainError> {
536 self.unconfigure_swapchain(device);
537
538 if let Some(window) = self.wl_window {
539 let library = libloading::Library::new("libwayland-egl.so").unwrap();
540 let wl_egl_window_resize: libloading::Symbol<WlEglWindowResizeFun> =
541 library.get(b"wl_egl_window_resize").unwrap();
542 wl_egl_window_resize(
543 window,
544 config.extent.width as i32,
545 config.extent.height as i32,
546 0,
547 0,
548 );
549 }
550
551 let desc = conv::describe_format(config.format).unwrap();
552
553 let gl = &device.share.context;
554 let renderbuffer = gl.create_renderbuffer().unwrap();
555 gl.bind_renderbuffer(glow::RENDERBUFFER, Some(renderbuffer));
556 gl.renderbuffer_storage(
557 glow::RENDERBUFFER,
558 desc.tex_internal,
559 config.extent.width as _,
560 config.extent.height as _,
561 );
562 let framebuffer = gl.create_framebuffer().unwrap();
563 gl.bind_framebuffer(glow::READ_FRAMEBUFFER, Some(framebuffer));
564 gl.framebuffer_renderbuffer(
565 glow::READ_FRAMEBUFFER,
566 glow::COLOR_ATTACHMENT0,
567 glow::RENDERBUFFER,
568 Some(renderbuffer),
569 );
570 gl.bind_renderbuffer(glow::RENDERBUFFER, None);
571 gl.bind_framebuffer(glow::READ_FRAMEBUFFER, None);
572
573 self.swapchain = Some(Swapchain {
574 renderbuffer,
575 framebuffer,
576 extent: config.extent,
577 format: desc.tex_internal,
578 channel: config.format.base_format().1,
579 });
580
581 Ok(())
582 }
583
584 unsafe fn unconfigure_swapchain(&mut self, device: &crate::Device) {
585 let gl = &device.share.context;
586 if let Some(sc) = self.swapchain.take() {
587 gl.delete_renderbuffer(sc.renderbuffer);
588 gl.delete_framebuffer(sc.framebuffer);
589 }
590 }
591
592 unsafe fn acquire_image(
593 &mut self,
594 _timeout_ns: u64,
595 ) -> Result<(Self::SwapchainImage, Option<w::Suboptimal>), w::AcquireError> {
596 let sc = self.swapchain.as_ref().unwrap();
597 let sc_image =
598 native::SwapchainImage::new(sc.renderbuffer, sc.format, sc.extent, sc.channel);
599 Ok((sc_image, None))
600 }
601}
602
603impl w::Surface<crate::Backend> for Surface {
604 fn supports_queue_family(&self, _: &crate::QueueFamily) -> bool {
605 self.presentable
606 }
607
608 fn capabilities(&self, _physical_device: &PhysicalDevice) -> w::SurfaceCapabilities {
609 w::SurfaceCapabilities {
610 present_modes: w::PresentMode::FIFO, composite_alpha_modes: w::CompositeAlphaMode::OPAQUE, image_count: 2..=2,
613 current_extent: None,
614 extents: w::Extent2D {
615 width: 4,
616 height: 4,
617 }..=w::Extent2D {
618 width: 4096,
619 height: 4096,
620 },
621 max_image_layers: 1,
622 usage: image::Usage::COLOR_ATTACHMENT,
623 }
624 }
625
626 fn supported_formats(
627 &self,
628 _physical_device: &PhysicalDevice,
629 ) -> Option<Vec<hal::format::Format>> {
630 use hal::format::Format;
631 Some(vec![Format::Rgba8Srgb, Format::Bgra8Srgb])
632 }
633}
634
635impl Surface {
636 pub(crate) unsafe fn present(
637 &mut self,
638 _image: native::SwapchainImage,
639 gl: &GlContainer,
640 ) -> Result<Option<w::Suboptimal>, w::PresentError> {
641 let sc = self.swapchain.as_ref().unwrap();
642
643 self.egl
644 .make_current(
645 self.display,
646 Some(self.raw),
647 Some(self.raw),
648 Some(self.context),
649 )
650 .unwrap();
651 gl.bind_framebuffer(glow::DRAW_FRAMEBUFFER, None);
652 gl.bind_framebuffer(glow::READ_FRAMEBUFFER, Some(sc.framebuffer));
653 gl.blit_framebuffer(
654 0,
655 0,
656 sc.extent.width as _,
657 sc.extent.height as _,
658 0,
659 0,
660 sc.extent.width as _,
661 sc.extent.height as _,
662 glow::COLOR_BUFFER_BIT,
663 glow::NEAREST,
664 );
665 gl.bind_framebuffer(glow::READ_FRAMEBUFFER, None);
666
667 self.egl.swap_buffers(self.display, self.raw).unwrap();
668
669 self.egl
670 .make_current(self.display, self.pbuffer, self.pbuffer, Some(self.context))
671 .unwrap();
672
673 Ok(None)
674 }
675}