From 1679584364dc70aa2fcf7e9646dab3dc470a44d0 Mon Sep 17 00:00:00 2001 From: i509VCB Date: Fri, 11 Nov 2022 23:31:57 -0600 Subject: [PATCH] api/egl: Add EGLDevice wrappers 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. --- .gitignore | 1 - CHANGELOG.md | 2 + glutin/src/api/egl/config.rs | 4 +- glutin/src/api/egl/context.rs | 30 ++++ glutin/src/api/egl/device.rs | 157 +++++++++++++++++++++ glutin/src/api/egl/display.rs | 187 +++++++++++++++++++++---- glutin/src/api/egl/mod.rs | 7 + glutin_egl_sys/build.rs | 1 + glutin_egl_sys/src/lib.rs | 5 +- glutin_examples/Cargo.toml | 7 +- glutin_examples/examples/egl_device.rs | 142 +++++++++++++++++++ glutin_examples/src/lib.rs | 11 +- 12 files changed, 518 insertions(+), 36 deletions(-) create mode 100644 glutin/src/api/egl/device.rs create mode 100644 glutin_examples/examples/egl_device.rs diff --git a/.gitignore b/.gitignore index 0584bb40fb..4461cbc942 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,3 @@ target/ .DS_Store *~ #*# -headless.png diff --git a/CHANGELOG.md b/CHANGELOG.md index 020da65cbb..fd100c930e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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)`. diff --git a/glutin/src/api/egl/config.rs b/glutin/src/api/egl/config.rs index bf03db28f7..2d786c93a4 100644 --- a/glutin/src/api/egl/config.rs +++ b/glutin/src/api/egl/config.rs @@ -308,7 +308,7 @@ impl GlConfig for Config { #[cfg(any(wayland_platform, x11_platform))] fn supports_transparency(&self) -> Option { 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()) @@ -356,7 +356,7 @@ impl AsRawConfig for Config { #[cfg(x11_platform)] impl X11GlConfigExt for Config { fn x11_visual(&self) -> Option { - 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 _) diff --git a/glutin/src/api/egl/context.rs b/glutin/src/api/egl/context.rs index ea534cec00..e45cddd560 100644 --- a/glutin/src/api/egl/context.rs +++ b/glutin/src/api/egl/context.rs @@ -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 { + self.inner.make_current_surfaceless()?; + Ok(PossiblyCurrentContext { inner: self.inner, _nosendsync: PhantomData }) + } + fn new(inner: ContextInner) -> Self { Self { inner } } @@ -225,6 +232,13 @@ pub struct PossiblyCurrentContext { _nosendsync: PhantomData, } +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; @@ -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( &self, surface_draw: &Surface, diff --git a/glutin/src/api/egl/device.rs b/glutin/src/api/egl/device.rs new file mode 100644 index 0000000000..465fb6050c --- /dev/null +++ b/glutin/src/api/egl/device.rs @@ -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, + vendor: Option, +} + +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> { + 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 is + // > less than or equal to zero unless is NULL, or if + // > 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 { + 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 { + // 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. 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 }) + } +} diff --git a/glutin/src/api/egl/display.rs b/glutin/src/api/egl/display.rs index 83ef46d08c..668c3efba1 100644 --- a/glutin/src/api/egl/display.rs +++ b/glutin/src/api/egl/display.rs @@ -2,9 +2,10 @@ use std::collections::HashSet; use std::ffi::{self, CStr}; -use std::fmt; use std::ops::Deref; +use std::os::raw::c_char; use std::sync::Arc; +use std::{fmt, ptr}; use glutin_egl_sys::egl; use glutin_egl_sys::egl::types::{EGLAttrib, EGLDisplay, EGLint}; @@ -23,12 +24,13 @@ use crate::surface::{PbufferSurface, PixmapSurface, SurfaceAttributes, WindowSur use super::config::Config; use super::context::NotCurrentContext; +use super::device::Device; use super::surface::Surface; use super::{Egl, EGL}; /// Extensions that don't require any display. -static NO_DISPLAY_EXTENSIONS: OnceCell> = OnceCell::new(); +pub(crate) static NO_DISPLAY_EXTENSIONS: OnceCell> = OnceCell::new(); /// A wrapper for the `EGLDisplay` and its supported extensions. #[derive(Debug, Clone)] @@ -72,28 +74,113 @@ impl Display { } })?; - let version = unsafe { - let (mut major, mut minor) = (0, 0); - if egl.Initialize(display, &mut major, &mut minor) == egl::FALSE { - return Err(super::check_error().err().unwrap()); - } + Self::initialize_display(egl, display) + } - Version::new(major as u8, minor as u8) + /// Create an EGL display using the specified device. + /// + /// In most cases, prefer [`Display::new`] unless you need to render + /// off screen or use other extensions like EGLStreams. + /// + /// This function may take an optional [`RawDisplayHandle`] argument. At the + /// moment the `raw_display` argument is ignored and this function will + /// return [`Err`]. This may change in the future. + /// + /// # Safety + /// + /// If `raw_display` is [`Some`], `raw_display` must point to a valid system + /// display. + pub unsafe fn with_device( + device: &Device, + raw_display: Option, + ) -> Result { + let egl = match EGL.as_ref() { + Some(egl) => egl, + None => return Err(ErrorKind::NotFound.into()), }; - // Load extensions. - let client_extensions = get_extensions(egl, display); - let features = Self::extract_display_features(&client_extensions, version); + if raw_display.is_some() { + return Err(ErrorKind::NotSupported( + "Display::with_device does not support a `raw_display` argument yet", + ) + .into()); + } - let inner = Arc::new(DisplayInner { - egl, - raw: EglDisplay(display), - _native_display: NativeDisplay(raw_display), - version, - features, - client_extensions, - }); - Ok(Self { inner }) + if !egl.GetPlatformDisplayEXT.is_loaded() { + return Err(ErrorKind::NotSupported("eglGetPlatformDisplayEXT is not supported").into()); + } + + // Okay to unwrap here because the client extensions must have been enumerated + // while querying the available devices or the device was gotten from an + // existing display. + let extensions = NO_DISPLAY_EXTENSIONS.get().unwrap(); + + if !extensions.contains("EGL_EXT_platform_base") + && !extensions.contains("EGL_EXT_platform_device") + { + return Err(ErrorKind::NotSupported( + "Creating a display from a device is not supported", + ) + .into()); + } + + let mut attrs = Vec::::with_capacity(2); + + // TODO: Some extensions exist like EGL_EXT_device_drm which allow specifying + // which DRM master fd to use under the hood by the implementation. This would + // mean there would need to be an unsafe equivalent to this function. + + // Push `egl::NONE` to terminate the list. + attrs.push(egl::NONE as EGLint); + + let display = Self::check_display_error(unsafe { + egl.GetPlatformDisplayEXT( + egl::PLATFORM_DEVICE_EXT, + device.raw_device() as *mut _, + attrs.as_ptr(), + ) + })?; + + Self::initialize_display(egl, display) + } + + /// Get the [`Device`] the display is using. + /// + /// This function returns [`Err`] if the `EGL_EXT_device_query` or + /// `EGL_EXT_device_base` extensions are not available. + pub fn device(&self) -> Result { + let no_display_extensions = NO_DISPLAY_EXTENSIONS.get().unwrap(); + + // Querying the device of a display only requires EGL_EXT_device_query, but we + // also check if EGL_EXT_device_base is available since + // EGL_EXT_device_base also provides EGL_EXT_device_query. + if !no_display_extensions.contains("EGL_EXT_device_query") + || !no_display_extensions.contains("EGL_EXT_device_base") + { + return Err(ErrorKind::NotSupported( + "Querying the device from a display is not supported", + ) + .into()); + } + + let device = ptr::null_mut(); + if unsafe { + self.inner.egl.QueryDisplayAttribEXT( + self.inner.raw.0, + egl::DEVICE_EXT as EGLint, + device as *mut _, + ) + } == egl::FALSE + { + // Check for EGL_NOT_INITIALIZED in case the display was externally terminated. + // + // EGL_BAD_ATTRIBUTE shouldn't be returned since EGL_DEVICE_EXT should be a + // valid display attribute. + super::check_error()?; + return Err(ErrorKind::NotSupported("Failed to query device from display").into()); + } + + Device::from_ptr(self.inner.egl, device) } fn get_platform_display(egl: &Egl, display: RawDisplayHandle) -> Result { @@ -257,6 +344,31 @@ impl Display { Ok(display) } } + + fn initialize_display(egl: &'static Egl, display: EGLDisplay) -> Result { + let version = unsafe { + let (mut major, mut minor) = (0, 0); + if egl.Initialize(display, &mut major, &mut minor) == egl::FALSE { + return Err(super::check_error().err().unwrap()); + } + + Version::new(major as u8, minor as u8) + }; + + // Load extensions. + let client_extensions = get_extensions(egl, display); + let features = Self::extract_display_features(&client_extensions, version); + + let inner = Arc::new(DisplayInner { + egl, + raw: EglDisplay(display), + _native_display: None, + version, + client_extensions, + features, + }); + Ok(Self { inner }) + } } impl GlDisplay for Display { @@ -349,7 +461,7 @@ pub(crate) struct DisplayInner { pub(crate) features: DisplayFeatures, /// The raw display used to create EGL display. - pub(crate) _native_display: NativeDisplay, + pub(crate) _native_display: Option, } impl fmt::Debug for DisplayInner { @@ -450,17 +562,32 @@ impl Deref for EglDisplay { } /// Collect EGL extensions for the given `display`. -fn get_extensions(egl: &Egl, display: EGLDisplay) -> HashSet<&'static str> { +pub(crate) fn get_extensions(egl: &Egl, display: EGLDisplay) -> HashSet<&'static str> { unsafe { let extensions = egl.QueryString(display, egl::EXTENSIONS as i32); - if extensions.is_null() { - return HashSet::new(); - } + // SAFETY: The EGL specification guarantees the returned string is + // static and null terminated: + // + // > eglQueryString returns a pointer to a static, zero-terminated + // > string describing properties of the EGL client or of an EGL + // > display connection. + extensions_from_ptr(extensions) + } +} - if let Ok(extensions) = CStr::from_ptr(extensions).to_str() { - extensions.split(' ').collect::>() - } else { - HashSet::new() - } +/// # Safety +/// +/// - The `extensions` pointer must be NULL (representing no extensions) or it +/// must be non-null and contain a static, null terminated C string. +pub(crate) unsafe fn extensions_from_ptr(extensions: *const c_char) -> HashSet<&'static str> { + if extensions.is_null() { + return HashSet::new(); + } + + // SAFETY: The caller has ensured the string pointer is null terminated. + if let Ok(extensions) = unsafe { CStr::from_ptr(extensions) }.to_str() { + extensions.split(' ').collect::>() + } else { + HashSet::new() } } diff --git a/glutin/src/api/egl/mod.rs b/glutin/src/api/egl/mod.rs index e7605c6ca7..c39cc16ed1 100644 --- a/glutin/src/api/egl/mod.rs +++ b/glutin/src/api/egl/mod.rs @@ -1,4 +1,10 @@ //! EGL platform Api. +//! +//! This platform is typically available on Linux, Android and other Unix-like +//! platforms. +//! +//! The EGL platform allows creating a [`Display`](self::display::Display) from +//! a [`Device`](self::device::Device). use std::ffi::{self, CString}; use std::ops::{Deref, DerefMut}; @@ -18,6 +24,7 @@ use crate::lib_loading::{SymLoading, SymWrapper}; pub mod config; pub mod context; +pub mod device; pub mod display; pub mod surface; diff --git a/glutin_egl_sys/build.rs b/glutin_egl_sys/build.rs index 594dcc2096..ccef77db20 100644 --- a/glutin_egl_sys/build.rs +++ b/glutin_egl_sys/build.rs @@ -28,6 +28,7 @@ fn main() { "EGL_EXT_device_drm_render_node", "EGL_EXT_device_enumeration", "EGL_EXT_device_query", + "EGL_EXT_device_query_name", "EGL_EXT_pixel_format_float", "EGL_EXT_platform_base", "EGL_EXT_platform_device", diff --git a/glutin_egl_sys/src/lib.rs b/glutin_egl_sys/src/lib.rs index d291001cb5..bb5d019f41 100644 --- a/glutin_egl_sys/src/lib.rs +++ b/glutin_egl_sys/src/lib.rs @@ -26,9 +26,12 @@ pub mod egl { include!(concat!(env!("OUT_DIR"), "/egl_bindings.rs")); - // TODO should upstream this. + // TODO should upstream these: + // EGL_EXT_platform_xcb pub const PLATFORM_XCB_EXT: super::EGLenum = 0x31DC; pub const PLATFORM_XCB_SCREEN_EXT: super::EGLenum = 0x31DC; + // EGL_EXT_device_query_name + pub const RENDERER_EXT: super::EGLenum = 0x335F; } pub use self::egl::types::{EGLContext, EGLDisplay}; diff --git a/glutin_examples/Cargo.toml b/glutin_examples/Cargo.toml index d426da0a49..3c17706c9c 100644 --- a/glutin_examples/Cargo.toml +++ b/glutin_examples/Cargo.toml @@ -12,7 +12,7 @@ publish = false [features] default = ["egl", "glx", "x11", "wayland", "wgl"] -egl = ["glutin/egl"] +egl = ["glutin/egl", "png"] glx = ["glutin/glx", "glutin/x11", "winit/x11", "x11"] wgl = ["glutin/wgl"] x11 = ["glutin/x11", "winit/x11"] @@ -22,6 +22,7 @@ wayland = ["glutin/wayland", "winit/wayland", "winit/wayland-dlopen", "winit/way glutin = { path = "../glutin", default-features = false } winit = { version = "0.27.2", default-features = false } raw-window-handle = "0.5.0" +png = { version = "0.17.6", optional = true } [target.'cfg(target_os = "android")'.dependencies] ndk-glue = "0.7" # Keep in sync with winit dependency @@ -33,3 +34,7 @@ cfg_aliases = "0.1.1" [[example]] name = "android" crate-type = ["cdylib"] + +[[example]] +name = "egl_device" +required-features = ["egl"] diff --git a/glutin_examples/examples/egl_device.rs b/glutin_examples/examples/egl_device.rs new file mode 100644 index 0000000000..86189f9a57 --- /dev/null +++ b/glutin_examples/examples/egl_device.rs @@ -0,0 +1,142 @@ +fn main() { + #[cfg(egl_backend)] + example::run(); +} + +#[cfg(egl_backend)] +mod example { + use std::fs::OpenOptions; + use std::path::Path; + + use glutin::api::egl::device::Device; + use glutin::api::egl::display::Display; + use glutin::config::{ConfigSurfaceTypes, ConfigTemplate, ConfigTemplateBuilder}; + use glutin::context::{ContextApi, ContextAttributesBuilder}; + use glutin::prelude::*; + use glutin_examples::{gl, Renderer}; + + const IMG_PATH: &str = concat!(env!("OUT_DIR"), "/egl_device.png"); + + pub fn run() { + let devices = Device::query_devices().expect("Failed to query devices").collect::>(); + + for (index, device) in devices.iter().enumerate() { + println!( + "Device {}: Name: {} Vendor: {}", + index, + device.name().unwrap_or("UNKNOWN"), + device.vendor().unwrap_or("UNKNOWN") + ); + } + + let device = devices.first().expect("No available devices"); + + // Create a display using the device. + let display = + unsafe { Display::with_device(device, None) }.expect("Failed to create display"); + + let template = config_template(); + let config = unsafe { display.find_configs(template) } + .unwrap() + .reduce( + |config, acc| { + if config.num_samples() > acc.num_samples() { + config + } else { + acc + } + }, + ) + .expect("No available configs"); + + println!("Picked a config with {} samples", config.num_samples()); + + // Context creation. + // + // In particular, since we are doing offscreen rendering we have no raw window + // handle to provide. + let context_attributes = ContextAttributesBuilder::new().build(None); + + // Since glutin by default tries to create OpenGL core context, which may not be + // present we should try gles. + let fallback_context_attributes = + ContextAttributesBuilder::new().with_context_api(ContextApi::Gles(None)).build(None); + + let not_current = unsafe { + display.create_context(&config, &context_attributes).unwrap_or_else(|_| { + display + .create_context(&config, &fallback_context_attributes) + .expect("failed to create context") + }) + }; + + // Make the context current for rendering + let _context = not_current.make_current_surfaceless().unwrap(); + let renderer = Renderer::new(&display); + + // Create a framebuffer for offscreen rendering since we do not have a window. + let mut framebuffer = 0; + let mut renderbuffer = 0; + unsafe { + renderer.GenFramebuffers(1, &mut framebuffer); + renderer.GenRenderbuffers(1, &mut renderbuffer); + renderer.BindFramebuffer(gl::FRAMEBUFFER, framebuffer); + renderer.BindRenderbuffer(gl::RENDERBUFFER, renderbuffer); + renderer.RenderbufferStorage(gl::RENDERBUFFER, gl::RGBA, 1280, 720); + renderer.FramebufferRenderbuffer( + gl::FRAMEBUFFER, + gl::COLOR_ATTACHMENT0, + gl::RENDERBUFFER, + renderbuffer, + ); + } + + renderer.resize(1280, 720); + renderer.draw(); + + let mut buffer = Vec::::with_capacity(1280 * 720 * 4); + unsafe { + // Wait for the previous commands to finish before reading from the framebuffer. + renderer.Finish(); + // Download the framebuffer contents to the buffer. + renderer.ReadPixels( + 0, + 0, + 1280, + 720, + gl::RGBA, + gl::UNSIGNED_BYTE, + buffer.as_mut_ptr() as *mut _, + ); + buffer.set_len(1280 * 720 * 4); + } + + let path = Path::new(IMG_PATH); + let file = OpenOptions::new().write(true).create(true).open(&path).unwrap(); + + let mut encoder = png::Encoder::new(file, 1280, 720); + encoder.set_depth(png::BitDepth::Eight); + encoder.set_color(png::ColorType::Rgba); + let mut png_writer = encoder.write_header().unwrap(); + + png_writer.write_image_data(&buffer[..]).unwrap(); + png_writer.finish().unwrap(); + println!("Output rendered to: {}", path.display()); + + unsafe { + // Unbind the framebuffer and renderbuffer before deleting. + renderer.BindFramebuffer(gl::DRAW_FRAMEBUFFER, 0); + renderer.BindRenderbuffer(gl::RENDERBUFFER, 0); + renderer.DeleteFramebuffers(1, &framebuffer); + renderer.DeleteRenderbuffers(1, &renderbuffer); + } + } + + fn config_template() -> ConfigTemplate { + ConfigTemplateBuilder::default() + .with_alpha_size(8) + // Offscreen rendering has no support window surface support. + .with_surface_type(ConfigSurfaceTypes::empty()) + .build() + } +} diff --git a/glutin_examples/src/lib.rs b/glutin_examples/src/lib.rs index 1647b1773c..9fb9f4faf0 100644 --- a/glutin_examples/src/lib.rs +++ b/glutin_examples/src/lib.rs @@ -4,6 +4,7 @@ use std::ffi::{CStr, CString}; use std::num::NonZeroU32; +use std::ops::Deref; use raw_window_handle::{ HasRawDisplayHandle, HasRawWindowHandle, RawDisplayHandle, RawWindowHandle, @@ -294,7 +295,7 @@ pub struct Renderer { } impl Renderer { - pub fn new(gl_display: &Display) -> Self { + pub fn new(gl_display: &D) -> Self { unsafe { let gl = gl::Gl::load_with(|symbol| { let symbol = CString::new(symbol).unwrap(); @@ -386,6 +387,14 @@ impl Renderer { } } +impl Deref for Renderer { + type Target = gl::Gl; + + fn deref(&self) -> &Self::Target { + &self.gl + } +} + impl Drop for Renderer { fn drop(&mut self) { unsafe {