From 960555a426b0aae48ed36889b8c7feb48de19a6d Mon Sep 17 00:00:00 2001 From: Marijn Suijten Date: Wed, 4 Sep 2024 17:11:47 +0200 Subject: [PATCH] [wgpu-hal] Allow importing external WGL contexts as with EGL (#6152) --- CHANGELOG.md | 1 + Cargo.lock | 13 ++++- wgpu-hal/Cargo.toml | 6 +-- wgpu-hal/examples/raw-gles.rs | 4 +- wgpu-hal/src/gles/egl.rs | 6 ++- wgpu-hal/src/gles/wgl.rs | 96 +++++++++++++++++++++++++++-------- 6 files changed, 96 insertions(+), 30 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5385a7e26..c3fe5c417 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -118,6 +118,7 @@ By @teoxoy [#6134](https://github.com/gfx-rs/wgpu/pull/6134). - Replace `winapi` code in WGL wrapper to use the `windows` crate. By @MarijnS95 in [#6006](https://github.com/gfx-rs/wgpu/pull/6006) - Update `glutin` to `0.31` with `glutin-winit` crate. By @MarijnS95 in [#6150](https://github.com/gfx-rs/wgpu/pull/6150) and [#6176](https://github.com/gfx-rs/wgpu/pull/6176) +- Implement `Adapter::new_external()` for WGL (just like EGL) to import an external OpenGL ES context. By @MarijnS95 in [#6152](https://github.com/gfx-rs/wgpu/pull/6152) #### DX12 diff --git a/Cargo.lock b/Cargo.lock index aaf0685b8..c382223c4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1330,12 +1330,14 @@ dependencies = [ "core-foundation", "dispatch", "glutin_egl_sys", + "glutin_wgl_sys 0.5.0", "icrate", "libloading", "objc2", "once_cell", "raw-window-handle 0.5.2", "wayland-sys", + "windows-sys 0.48.0", "x11-dl", ] @@ -1361,6 +1363,15 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "glutin_wgl_sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c8098adac955faa2d31079b65dc48841251f69efd3ac25477903fc424362ead" +dependencies = [ + "gl_generator", +] + [[package]] name = "glutin_wgl_sys" version = "0.6.0" @@ -3669,7 +3680,7 @@ dependencies = [ "glow", "glutin", "glutin-winit", - "glutin_wgl_sys", + "glutin_wgl_sys 0.6.0", "gpu-alloc", "gpu-allocator", "gpu-descriptor", diff --git a/wgpu-hal/Cargo.toml b/wgpu-hal/Cargo.toml index 24f8b7ed1..d3a05f079 100644 --- a/wgpu-hal/Cargo.toml +++ b/wgpu-hal/Cargo.toml @@ -206,8 +206,8 @@ env_logger.workspace = true glam.workspace = true # for ray-traced-triangle example winit.workspace = true # for "halmark" example -[target.'cfg(not(any(target_arch = "wasm32", windows, target_os = "ios")))'.dev-dependencies] -glutin-winit = { workspace = true, features = ["egl", "wayland", "x11"] } # for "raw-gles" example -glutin = { workspace = true, features = ["egl", "wayland", "x11"] } # for "raw-gles" example +[target.'cfg(not(any(target_arch = "wasm32", target_os = "ios")))'.dev-dependencies] +glutin-winit = { workspace = true, features = ["egl", "wgl", "wayland", "x11"] } # for "raw-gles" example +glutin = { workspace = true, features = ["egl", "wgl", "wayland", "x11"] } # for "raw-gles" example rwh_05 = { version = "0.5", package = "raw-window-handle" } # temporary compatibility for glutin-winit in "raw-gles" example winit = { workspace = true, features = ["rwh_05"] } # for "raw-gles" example diff --git a/wgpu-hal/examples/raw-gles.rs b/wgpu-hal/examples/raw-gles.rs index 34298d081..2743ce9b8 100644 --- a/wgpu-hal/examples/raw-gles.rs +++ b/wgpu-hal/examples/raw-gles.rs @@ -10,7 +10,7 @@ extern crate wgpu_hal as hal; -#[cfg(not(any(windows, target_arch = "wasm32", target_os = "ios")))] +#[cfg(not(any(target_arch = "wasm32", target_os = "ios")))] fn main() { use std::{ffi::CString, num::NonZeroU32}; @@ -255,7 +255,6 @@ fn main() { } #[cfg(any( - windows, all(target_arch = "wasm32", not(target_os = "emscripten")), target_os = "ios" ))] @@ -264,7 +263,6 @@ fn main() { } #[cfg(not(any( - windows, all(target_arch = "wasm32", not(target_os = "emscripten")), target_os = "ios" )))] diff --git a/wgpu-hal/src/gles/egl.rs b/wgpu-hal/src/gles/egl.rs index 31b93da4d..42aec2b25 100644 --- a/wgpu-hal/src/gles/egl.rs +++ b/wgpu-hal/src/gles/egl.rs @@ -379,7 +379,7 @@ struct EglContextLock<'a> { display: khronos_egl::Display, } -/// A guard containing a lock to an [`AdapterContext`] +/// A guard containing a lock to an [`AdapterContext`], while the GL context is kept current. pub struct AdapterContextLock<'a> { glow: MutexGuard<'a, ManuallyDrop>, egl: Option>, @@ -1082,7 +1082,9 @@ impl crate::Instance for Instance { unsafe { gl.debug_message_callback(super::gl_debug_message_callback) }; } - // Avoid accidental drop when the context is not current. + // Wrap in ManuallyDrop to make it easier to "current" the GL context before dropping this + // GLOW context, which could also happen if a panic occurs after we uncurrent the context + // below but before AdapterContext is constructed. let gl = ManuallyDrop::new(gl); inner.egl.unmake_current(); diff --git a/wgpu-hal/src/gles/wgl.rs b/wgpu-hal/src/gles/wgl.rs index 67d8cf61d..2d6c91aee 100644 --- a/wgpu-hal/src/gles/wgl.rs +++ b/wgpu-hal/src/gles/wgl.rs @@ -1,11 +1,3 @@ -use glow::HasContext; -use glutin_wgl_sys::wgl_extra::{ - Wgl, CONTEXT_CORE_PROFILE_BIT_ARB, CONTEXT_DEBUG_BIT_ARB, CONTEXT_FLAGS_ARB, - CONTEXT_PROFILE_MASK_ARB, -}; -use once_cell::sync::Lazy; -use parking_lot::{Mutex, MutexGuard, RwLock}; -use raw_window_handle::{RawDisplayHandle, RawWindowHandle}; use std::{ collections::HashSet, ffi::{c_void, CStr, CString}, @@ -19,6 +11,15 @@ use std::{ thread, time::Duration, }; + +use glow::HasContext; +use glutin_wgl_sys::wgl_extra::{ + Wgl, CONTEXT_CORE_PROFILE_BIT_ARB, CONTEXT_DEBUG_BIT_ARB, CONTEXT_FLAGS_ARB, + CONTEXT_PROFILE_MASK_ARB, +}; +use once_cell::sync::Lazy; +use parking_lot::{Mutex, MutexGuard, RwLock}; +use raw_window_handle::{RawDisplayHandle, RawWindowHandle}; use wgt::InstanceFlags; use windows::{ core::{Error, PCSTR}, @@ -48,7 +49,10 @@ impl AdapterContext { } pub fn raw_context(&self) -> *mut c_void { - self.inner.lock().context.context.0 + match self.inner.lock().context { + Some(ref wgl) => wgl.context.0, + None => ptr::null_mut(), + } } /// Obtain a lock to the WGL context and get handle to the [`glow::Context`] that can be used to @@ -62,7 +66,9 @@ impl AdapterContext { .try_lock_for(Duration::from_secs(CONTEXT_LOCK_TIMEOUT_SECS)) .expect("Could not lock adapter context. This is most-likely a deadlock."); - inner.context.make_current(inner.device.dc).unwrap(); + if let Some(wgl) = &inner.context { + wgl.make_current(inner.device.dc).unwrap() + }; AdapterContextLock { inner } } @@ -79,14 +85,15 @@ impl AdapterContext { .try_lock_for(Duration::from_secs(CONTEXT_LOCK_TIMEOUT_SECS)) .expect("Could not lock adapter context. This is most-likely a deadlock."); - inner - .context - .make_current(device) - .map(|()| AdapterContextLock { inner }) + if let Some(wgl) = &inner.context { + wgl.make_current(device)?; + } + + Ok(AdapterContextLock { inner }) } } -/// A guard containing a lock to an [`AdapterContext`] +/// A guard containing a lock to an [`AdapterContext`], while the GL context is kept current. pub struct AdapterContextLock<'a> { inner: MutexGuard<'a, Inner>, } @@ -101,7 +108,9 @@ impl<'a> std::ops::Deref for AdapterContextLock<'a> { impl<'a> Drop for AdapterContextLock<'a> { fn drop(&mut self) { - self.inner.context.unmake_current().unwrap(); + if let Some(wgl) = &self.inner.context { + wgl.unmake_current().unwrap() + } } } @@ -136,7 +145,7 @@ unsafe impl Sync for WglContext {} struct Inner { gl: ManuallyDrop, device: InstanceDevice, - context: WglContext, + context: Option, } impl Drop for Inner { @@ -150,8 +159,14 @@ impl Drop for Inner { // Context must be current when dropped. See safety docs on // `glow::HasContext`. - self.context.make_current(self.device.dc).unwrap(); - let _guard = CurrentGuard(&self.context); + // + // NOTE: This is only set to `None` by `Adapter::new_external` which + // requires the context to be current when anything that may be holding + // the `Arc` is dropped. + let _guard = self.context.as_ref().map(|wgl| { + wgl.make_current(self.device.dc).unwrap(); + CurrentGuard(wgl) + }); // SAFETY: Field not used after this. unsafe { ManuallyDrop::drop(&mut self.gl) }; } @@ -510,7 +525,9 @@ impl crate::Instance for Instance { unsafe { gl.debug_message_callback(super::gl_debug_message_callback) }; } - // Avoid accidental drop when the context is not current. + // Wrap in ManuallyDrop to make it easier to "current" the GL context before dropping this + // GLOW context, which could also happen if a panic occurs after we uncurrent the context + // below but before Inner is constructed. let gl = ManuallyDrop::new(gl); context.unmake_current().map_err(|e| { crate::InstanceError::with_source( @@ -523,7 +540,7 @@ impl crate::Instance for Instance { inner: Arc::new(Mutex::new(Inner { device, gl, - context, + context: Some(context), })), srgb_capable, }) @@ -565,6 +582,43 @@ impl crate::Instance for Instance { } } +impl super::Adapter { + /// Creates a new external adapter using the specified loader function. + /// + /// # Safety + /// + /// - The underlying OpenGL ES context must be current. + /// - The underlying OpenGL ES context must be current when interfacing with any objects returned by + /// wgpu-hal from this adapter. + /// - The underlying OpenGL ES context must be current when dropping this adapter and when + /// dropping any objects returned from this adapter. + pub unsafe fn new_external( + fun: impl FnMut(&str) -> *const c_void, + ) -> Option> { + let context = unsafe { glow::Context::from_loader_function(fun) }; + unsafe { + Self::expose(AdapterContext { + inner: Arc::new(Mutex::new(Inner { + gl: ManuallyDrop::new(context), + device: create_instance_device().ok()?, + context: None, + })), + }) + } + } + + pub fn adapter_context(&self) -> &AdapterContext { + &self.shared.context + } +} + +impl super::Device { + /// Returns the underlying WGL context. + pub fn context(&self) -> &AdapterContext { + &self.shared.context + } +} + struct DeviceContextHandle { device: Gdi::HDC, window: Foundation::HWND,