Skip to content

Commit

Permalink
api/egl: Add EGLDevice wrappers
Browse files Browse the repository at this point in the history
This allows querying the available EGL devices on the system and getting the
device used by the display. A device can provide information such as the name
of the device the vendor and in the future, other info such as the DRM device
node on Linux. Mesa does not implement the extensions at the moment to allow
getting the name and vendor of the device.

A headless Display can be created using a Device. Along with this addition, an
EGL specific api to make a context current without a surface is also available.
  • Loading branch information
i509VCB authored Nov 12, 2022
1 parent a460e99 commit 1679584
Show file tree
Hide file tree
Showing 12 changed files with 518 additions and 36 deletions.
1 change: 0 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,3 @@ target/
.DS_Store
*~
#*#
headless.png
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# Unreleased

- Added `EGLDevice` wrappers for EGL.
- Added EGL dependent api to make a context current without a surface.
- Added `supports_transparency` on `GlConfig`.
- On GLX, try all extensions when setting vsync.
- On WGL, fixed that `Surface::swap_buffers` takes longer with every call caused by frequent calls of the win32 function `HDC GetDC(HWND hWnd)`.
Expand Down
4 changes: 2 additions & 2 deletions glutin/src/api/egl/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -308,7 +308,7 @@ impl GlConfig for Config {
#[cfg(any(wayland_platform, x11_platform))]
fn supports_transparency(&self) -> Option<bool> {
use raw_window_handle::RawDisplayHandle;
match *self.inner.display.inner._native_display {
match *self.inner.display.inner._native_display? {
#[cfg(x11_platform)]
RawDisplayHandle::Xlib(_) | RawDisplayHandle::Xcb(_) => {
self.x11_visual().map(|visual| visual.supports_transparency())
Expand Down Expand Up @@ -356,7 +356,7 @@ impl AsRawConfig for Config {
#[cfg(x11_platform)]
impl X11GlConfigExt for Config {
fn x11_visual(&self) -> Option<X11VisualInfo> {
match *self.inner.display.inner._native_display {
match *self.inner.display.inner._native_display? {
raw_window_handle::RawDisplayHandle::Xlib(display_handle) => unsafe {
let xid = self.native_visual();
X11VisualInfo::from_xid(display_handle.display as *mut _, xid as _)
Expand Down
30 changes: 30 additions & 0 deletions glutin/src/api/egl/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,13 @@ pub struct NotCurrentContext {
}

impl NotCurrentContext {
/// Make a [`Self::PossiblyCurrentContext`] indicating that the context
/// could be current on the thread.
pub fn make_current_surfaceless(self) -> Result<PossiblyCurrentContext> {
self.inner.make_current_surfaceless()?;
Ok(PossiblyCurrentContext { inner: self.inner, _nosendsync: PhantomData })
}

fn new(inner: ContextInner) -> Self {
Self { inner }
}
Expand Down Expand Up @@ -225,6 +232,13 @@ pub struct PossiblyCurrentContext {
_nosendsync: PhantomData<EGLContext>,
}

impl PossiblyCurrentContext {
/// Make this context current on the calling thread.
pub fn make_current_surfaceless(&self) -> Result<()> {
self.inner.make_current_surfaceless()
}
}

impl PossiblyCurrentGlContext for PossiblyCurrentContext {
type NotCurrentContext = NotCurrentContext;

Expand Down Expand Up @@ -289,6 +303,22 @@ pub(crate) struct ContextInner {
}

impl ContextInner {
fn make_current_surfaceless(&self) -> Result<()> {
unsafe {
if self.display.inner.egl.MakeCurrent(
*self.display.inner.raw,
egl::NO_SURFACE,
egl::NO_SURFACE,
*self.raw,
) == egl::FALSE
{
super::check_error()
} else {
Ok(())
}
}
}

fn make_current_draw_read<T: SurfaceTypeTrait>(
&self,
surface_draw: &Surface<T>,
Expand Down
157 changes: 157 additions & 0 deletions glutin/src/api/egl/device.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
//! Everything related to `EGLDevice`.
use std::collections::HashSet;
use std::ffi::{c_void, CStr};
use std::ptr;

use glutin_egl_sys::egl;
use glutin_egl_sys::egl::types::EGLDeviceEXT;

use crate::error::{ErrorKind, Result};

use super::display::{extensions_from_ptr, get_extensions, NO_DISPLAY_EXTENSIONS};
use super::{Egl, EGL};

/// Wrapper for `EGLDevice`.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Device {
inner: EGLDeviceEXT,
extensions: HashSet<&'static str>,
name: Option<String>,
vendor: Option<String>,
}

impl Device {
/// Query the available devices.
///
/// This function returns [`Err`] if the `EGL_EXT_device_query` and
/// `EGL_EXT_device_enumeration` or `EGL_EXT_device_base` extensions are
/// not available.
pub fn query_devices() -> Result<impl Iterator<Item = Device>> {
let egl = match EGL.as_ref() {
Some(egl) => egl,
None => return Err(ErrorKind::NotFound.into()),
};

let no_display_extensions =
NO_DISPLAY_EXTENSIONS.get_or_init(|| get_extensions(egl, egl::NO_DISPLAY));

// Querying devices requires EGL_EXT_device_enumeration and
// EGL_EXT_device_query.
//
// Or we can check for the EGL_EXT_device_base extension since it contains both
// extensions.
if (!no_display_extensions.contains("EGL_EXT_device_enumeration")
&& !no_display_extensions.contains("EGL_EXT_device_query"))
|| !no_display_extensions.contains("EGL_EXT_device_base")
{
return Err(ErrorKind::NotSupported("EGL does not support EGL_EXT_device_base").into());
}

let mut device_count = 0;

if unsafe {
// The specification states:
// > An EGL_BAD_PARAMETER error is generated if <max_devices> is
// > less than or equal to zero unless <devices> is NULL, or if
// > <num_devices> is NULL.
//
// The error will never be generated since num_devices is a pointer
// to the count being queried. Therefore there is no need to check
// the error.
egl.QueryDevicesEXT(0, ptr::null_mut(), &mut device_count) == egl::FALSE
} {
super::check_error()?;
// On failure, EGL_FALSE is returned.
return Err(ErrorKind::NotSupported("Querying device count failed").into());
}

let mut devices = Vec::with_capacity(device_count as usize);

unsafe {
let mut count = device_count;
if egl.QueryDevicesEXT(device_count, devices.as_mut_ptr(), &mut count) == egl::FALSE {
super::check_error()?;
// On failure, EGL_FALSE is returned.
return Err(ErrorKind::NotSupported("Querying devices failed").into());
}

// SAFETY: EGL has initialized the vector for the number of devices.
devices.set_len(device_count as usize);
}

Ok(devices.into_iter().flat_map(|ptr| Device::from_ptr(egl, ptr)))
}

/// Get the device extensions supported by this device.
///
/// These extensions are distinct from the display extensions and should not
/// be used interchangeably.
pub fn extensions(&self) -> &HashSet<&str> {
&self.extensions
}

/// Get the name of the device.
///
/// This function will return [`None`] if the `EGL_EXT_device_query_name`
/// device extension is not available.
pub fn name(&self) -> Option<&str> {
self.name.as_deref()
}

/// Get the vendor of the device.
///
/// This function will return [`None`] if the `EGL_EXT_device_query_name`
/// device extension is not available.
pub fn vendor(&self) -> Option<&str> {
self.vendor.as_deref()
}

/// Get a raw handle to the `EGLDevice`.
pub fn raw_device(&self) -> *const c_void {
self.inner
}
}

// SAFETY: An EGLDevice is immutable and valid for the lifetime of the EGL
// library.
unsafe impl Send for Device {}
unsafe impl Sync for Device {}

impl Device {
unsafe fn query_string(egl_device: *const c_void, name: egl::types::EGLenum) -> Option<String> {
let egl = super::EGL.as_ref().unwrap();

// SAFETY: The caller has ensured the name is valid.
let ptr = unsafe { egl.QueryDeviceStringEXT(egl_device, name as _) };

if ptr.is_null() {
return None;
}

unsafe { CStr::from_ptr(ptr) }.to_str().ok().map(String::from)
}

pub(crate) fn from_ptr(egl: &Egl, ptr: *const c_void) -> Result<Self> {
// SAFETY: The EGL specification guarantees the returned string is
// static and null terminated:
//
// > eglQueryDeviceStringEXT returns a pointer to a static,
// > zero-terminated string describing some aspect of the specified
// > EGLDeviceEXT. <name> must be EGL_EXTENSIONS.
let extensions =
unsafe { extensions_from_ptr(egl.QueryDeviceStringEXT(ptr, egl::EXTENSIONS as _)) };

let (name, vendor) = if extensions.contains("EGL_EXT_device_query_name") {
// SAFETY: RENDERER_EXT and VENDOR are valid strings for device string queries
// if EGL_EXT_device_query_name.
unsafe {
(Self::query_string(ptr, egl::RENDERER_EXT), Self::query_string(ptr, egl::VENDOR))
}
} else {
(None, None)
};

Ok(Self { inner: ptr, extensions, name, vendor })
}
}
Loading

0 comments on commit 1679584

Please sign in to comment.