diff --git a/Cargo.lock b/Cargo.lock index 88e055e347..f5faf30922 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1189,6 +1189,16 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b" +[[package]] +name = "dispatch2" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89a09f22a6c6069a18470eb92d2298acf25463f14256d24778e1230d789a2aec" +dependencies = [ + "bitflags 2.10.0", + "objc2 0.6.3", +] + [[package]] name = "displaydoc" version = "0.2.5" @@ -1684,56 +1694,48 @@ dependencies = [ [[package]] name = "glutin" -version = "0.31.3" +version = "0.32.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18fcd4ae4e86d991ad1300b8f57166e5be0c95ef1f63f3f5b827f8a164548746" +checksum = "12124de845cacfebedff80e877bb37b5b75c34c5a4c89e47e1cdd67fb6041325" dependencies = [ "bitflags 2.10.0", - "cfg_aliases 0.1.1", + "cfg_aliases 0.2.1", "cgl", - "core-foundation 0.9.4", - "dispatch", + "dispatch2", "glutin_egl_sys", - "glutin_wgl_sys 0.5.0", - "icrate", + "glutin_wgl_sys", "libloading", - "objc2 0.4.1", + "objc2 0.6.3", + "objc2-app-kit 0.3.2", + "objc2-core-foundation", + "objc2-foundation 0.3.2", "once_cell", - "raw-window-handle 0.5.2", + "raw-window-handle 0.6.2", "wayland-sys", - "windows-sys 0.48.0", + "windows-sys 0.52.0", "x11-dl", ] [[package]] name = "glutin-winit" -version = "0.4.2" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ebcdfba24f73b8412c5181e56f092b5eff16671c514ce896b258a0a64bd7735" +checksum = "85edca7075f8fc728f28cb8fbb111a96c3b89e930574369e3e9c27eb75d3788f" dependencies = [ - "cfg_aliases 0.1.1", + "cfg_aliases 0.2.1", "glutin", - "raw-window-handle 0.5.2", - "winit 0.29.15", + "raw-window-handle 0.6.2", + "winit 0.30.12", ] [[package]] name = "glutin_egl_sys" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77cc5623f5309ef433c3dd4ca1223195347fe62c413da8e2fdd0eb76db2d9bcd" -dependencies = [ - "gl_generator", - "windows-sys 0.48.0", -] - -[[package]] -name = "glutin_wgl_sys" -version = "0.5.0" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c8098adac955faa2d31079b65dc48841251f69efd3ac25477903fc424362ead" +checksum = "4c4680ba6195f424febdc3ba46e7a42a0e58743f2edb115297b86d7f8ecc02d2" dependencies = [ "gl_generator", + "windows-sys 0.52.0", ] [[package]] @@ -2720,6 +2722,15 @@ dependencies = [ "objc2-encode 4.1.0", ] +[[package]] +name = "objc2" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c2599ce0ec54857b29ce62166b0ed9b4f6f1a70ccc9a71165b6154caca8c05" +dependencies = [ + "objc2-encode 4.1.0", +] + [[package]] name = "objc2-app-kit" version = "0.2.2" @@ -2732,10 +2743,22 @@ dependencies = [ "objc2 0.5.2", "objc2-core-data", "objc2-core-image", - "objc2-foundation", + "objc2-foundation 0.2.2", "objc2-quartz-core", ] +[[package]] +name = "objc2-app-kit" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d49e936b501e5c5bf01fda3a9452ff86dc3ea98ad5f283e1455153142d97518c" +dependencies = [ + "bitflags 2.10.0", + "objc2 0.6.3", + "objc2-core-foundation", + "objc2-foundation 0.3.2", +] + [[package]] name = "objc2-cloud-kit" version = "0.2.2" @@ -2746,7 +2769,7 @@ dependencies = [ "block2 0.5.1", "objc2 0.5.2", "objc2-core-location", - "objc2-foundation", + "objc2-foundation 0.2.2", ] [[package]] @@ -2757,7 +2780,7 @@ checksum = "a5ff520e9c33812fd374d8deecef01d4a840e7b41862d849513de77e44aa4889" dependencies = [ "block2 0.5.1", "objc2 0.5.2", - "objc2-foundation", + "objc2-foundation 0.2.2", ] [[package]] @@ -2769,7 +2792,18 @@ dependencies = [ "bitflags 2.10.0", "block2 0.5.1", "objc2 0.5.2", - "objc2-foundation", + "objc2-foundation 0.2.2", +] + +[[package]] +name = "objc2-core-foundation" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" +dependencies = [ + "bitflags 2.10.0", + "dispatch2", + "objc2 0.6.3", ] [[package]] @@ -2780,7 +2814,7 @@ checksum = "55260963a527c99f1819c4f8e3b47fe04f9650694ef348ffd2227e8196d34c80" dependencies = [ "block2 0.5.1", "objc2 0.5.2", - "objc2-foundation", + "objc2-foundation 0.2.2", "objc2-metal", ] @@ -2793,7 +2827,7 @@ dependencies = [ "block2 0.5.1", "objc2 0.5.2", "objc2-contacts", - "objc2-foundation", + "objc2-foundation 0.2.2", ] [[package]] @@ -2821,6 +2855,17 @@ dependencies = [ "objc2 0.5.2", ] +[[package]] +name = "objc2-foundation" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272" +dependencies = [ + "bitflags 2.10.0", + "objc2 0.6.3", + "objc2-core-foundation", +] + [[package]] name = "objc2-link-presentation" version = "0.2.2" @@ -2829,8 +2874,8 @@ checksum = "a1a1ae721c5e35be65f01a03b6d2ac13a54cb4fa70d8a5da293d7b0020261398" dependencies = [ "block2 0.5.1", "objc2 0.5.2", - "objc2-app-kit", - "objc2-foundation", + "objc2-app-kit 0.2.2", + "objc2-foundation 0.2.2", ] [[package]] @@ -2842,7 +2887,7 @@ dependencies = [ "bitflags 2.10.0", "block2 0.5.1", "objc2 0.5.2", - "objc2-foundation", + "objc2-foundation 0.2.2", ] [[package]] @@ -2854,7 +2899,7 @@ dependencies = [ "bitflags 2.10.0", "block2 0.5.1", "objc2 0.5.2", - "objc2-foundation", + "objc2-foundation 0.2.2", "objc2-metal", ] @@ -2865,7 +2910,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0a684efe3dec1b305badae1a28f6555f6ddd3bb2c2267896782858d5a78404dc" dependencies = [ "objc2 0.5.2", - "objc2-foundation", + "objc2-foundation 0.2.2", ] [[package]] @@ -2881,7 +2926,7 @@ dependencies = [ "objc2-core-data", "objc2-core-image", "objc2-core-location", - "objc2-foundation", + "objc2-foundation 0.2.2", "objc2-link-presentation", "objc2-quartz-core", "objc2-symbols", @@ -2897,7 +2942,7 @@ checksum = "44fa5f9748dbfe1ca6c0b79ad20725a11eca7c2218bceb4b005cb1be26273bfe" dependencies = [ "block2 0.5.1", "objc2 0.5.2", - "objc2-foundation", + "objc2-foundation 0.2.2", ] [[package]] @@ -2910,7 +2955,7 @@ dependencies = [ "block2 0.5.1", "objc2 0.5.2", "objc2-core-location", - "objc2-foundation", + "objc2-foundation 0.2.2", ] [[package]] @@ -4840,7 +4885,6 @@ dependencies = [ "glow", "glutin", "glutin-winit", - "glutin_wgl_sys 0.6.1", "gpu-allocator", "gpu-descriptor", "hashbrown 0.16.1", @@ -4861,7 +4905,6 @@ dependencies = [ "portable-atomic-util", "profiling", "range-alloc", - "raw-window-handle 0.5.2", "raw-window-handle 0.6.2", "renderdoc-sys", "smallvec", @@ -5500,8 +5543,8 @@ dependencies = [ "memmap2", "ndk 0.9.0", "objc2 0.5.2", - "objc2-app-kit", - "objc2-foundation", + "objc2-app-kit 0.2.2", + "objc2-foundation 0.2.2", "objc2-ui-kit", "orbclient", "percent-encoding", diff --git a/Cargo.toml b/Cargo.toml index 5698d9d6fe..8793e46b9f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -172,7 +172,6 @@ pp-rs = "0.2.1" profiling = { version = "1.0.1", default-features = false } quote = "1.0.38" raw-window-handle = { version = "0.6.2", default-features = false } -rwh_05 = { version = "0.5.2", package = "raw-window-handle" } # temporary compatibility for glutin-winit rayon = "1.3" regex-lite = "0.1" renderdoc-sys = "1" @@ -225,9 +224,8 @@ gpu-allocator = { version = "0.28", default-features = false, features = [ # Gles dependencies khronos-egl = "6" glow = "0.16" -glutin = { version = "0.31", default-features = false } -glutin-winit = { version = "0.4", default-features = false } -glutin_wgl_sys = "0.6" +glutin = { version = "0.32.3", default-features = false } +glutin-winit = { version = "0.5", default-features = false } # DX12 and GLES dependencies windows = { version = "0.62", default-features = false } diff --git a/tests/src/init.rs b/tests/src/init.rs index 71567fd829..60bc2b4ba1 100644 --- a/tests/src/init.rs +++ b/tests/src/init.rs @@ -77,6 +77,7 @@ pub fn initialize_instance(backends: wgpu::Backends, params: &TestParameters) -> }, }, display: None, + window: None, }) } diff --git a/tests/tests/wgpu-validation/api/instance.rs b/tests/tests/wgpu-validation/api/instance.rs index 9e231daf9c..6ca09ccf34 100644 --- a/tests/tests/wgpu-validation/api/instance.rs +++ b/tests/tests/wgpu-validation/api/instance.rs @@ -42,6 +42,7 @@ mod request_adapter_error { memory_budget_thresholds: wgpu::MemoryBudgetThresholds::default(), backend_options: wgpu::BackendOptions::default(), display: None, + window: None, } } diff --git a/wgpu-core/src/instance.rs b/wgpu-core/src/instance.rs index 508543b18c..d9e44751c3 100644 --- a/wgpu-core/src/instance.rs +++ b/wgpu-core/src/instance.rs @@ -96,6 +96,13 @@ pub struct Instance { /// When used with `winit`, callers are expected to pass its `OwnedDisplayHandle` (created from /// the `EventLoop`) here. display: Option>, + + /// Non-lifetimed [`raw_window_handle::WindowHandle`], for keepalive and validation purposes in + /// [`Self::create_surface()`]. + /// + /// When used with `winit`, callers are expected to pass its `OwnedDisplayHandle` (created from + /// the `EventLoop`) here. + window: Option>, } impl Instance { @@ -114,6 +121,7 @@ impl Instance { // try_add_hal(). Remove it from the mutable descriptor instead, while try_add_hal() // borrows the handle from `this.display` instead. display: instance_desc.display.take(), + window: instance_desc.window.take(), }; #[cfg(vulkan)] @@ -161,6 +169,10 @@ impl Instance { hdh.display_handle() .expect("Implementation did not provide a DisplayHandle") }), + window: self.window.as_ref().map(|hdw| { + hdw.window_handle() + .expect("Implementation did not provide a WindowHandle") + }), }; use hal::Instance as _; @@ -192,6 +204,7 @@ impl Instance { supported_backends: A::VARIANT.into(), flags: wgt::InstanceFlags::default(), display: None, // TODO: Extract display from HAL instance if available? + window: None, // TODO: Extract window from HAL instance if available? } } diff --git a/wgpu-hal/Cargo.toml b/wgpu-hal/Cargo.toml index b8ed67e3ac..db87c3f0b0 100644 --- a/wgpu-hal/Cargo.toml +++ b/wgpu-hal/Cargo.toml @@ -60,7 +60,7 @@ unexpected_cfgs = { level = "warn", check-cfg = [ # exclude the Vulkan backend on MacOS unless a separate feature `vulkan-portability` is enabled. In response # to these features, it enables features of platform specific crates. For example, the `vulkan` feature in wgpu-core # enables the `vulkan` feature in `wgpu-core-deps-windows-linux-android` which in turn enables the -# `vulkan` feature in `wgpu-hal` _only_ on those platforms. If you enable the `vulkan-portability` feature, it +# `vulkan` feature in `wgpu-hal` _only_ on those platforms. If you enable the `vulkan-portability` feature, it # will enable the `vulkan` feature in `wgpu-core-deps-apple`. The only way to do this is unfortunately to have # a separate crate for each platform category that participates in the feature unification. # @@ -110,7 +110,6 @@ gles = [ "dep:arrayvec", "dep:bytemuck", "dep:glow", - "dep:glutin_wgl_sys", "dep:hashbrown", "dep:js-sys", "dep:khronos-egl", @@ -232,6 +231,7 @@ ash = { workspace = true, optional = true } gpu-descriptor = { workspace = true, optional = true } smallvec = { workspace = true, optional = true, features = ["union"] } # Backend: GLES +glutin = { workspace = true, features = ["egl", "wgl", "wayland", "x11"] } khronos-egl = { workspace = true, features = ["dynamic"], optional = true } libloading = { workspace = true, optional = true } renderdoc-sys = { workspace = true, optional = true } @@ -262,8 +262,6 @@ windows-core = { workspace = true, optional = true } bit-set = { workspace = true, optional = true } range-alloc = { workspace = true, optional = true } once_cell = { workspace = true, optional = true } -# backend: GLES -glutin_wgl_sys = { workspace = true, optional = true } ### Platform: x86/x86_64 Windows ### # This doesn't support aarch64. See https://github.com/gfx-rs/wgpu/issues/6860. @@ -342,7 +340,5 @@ winit.workspace = true # for "halmark" ### Platform: Windows + MacOS + Linux for "raw-gles" example ### [target.'cfg(not(any(target_arch = "wasm32", target_os = "ios", target_os = "visionos")))'.dev-dependencies] glutin-winit = { workspace = true, features = ["egl", "wgl", "wayland", "x11"] } -glutin = { workspace = true, features = ["egl", "wgl", "wayland", "x11"] } # temporary compatibility for glutin-winit -rwh_05.workspace = true winit = { workspace = true, features = ["rwh_05"] } diff --git a/wgpu-hal/build.rs b/wgpu-hal/build.rs index 7bb56e99b2..f3410de155 100644 --- a/wgpu-hal/build.rs +++ b/wgpu-hal/build.rs @@ -1,31 +1,48 @@ fn main() { cfg_aliases::cfg_aliases! { - native: { not(target_arch = "wasm32") }, + // platforms + android_platform: { target_os = "android" }, + ohos_platform: { target_env = "ohos" }, + wasm_platform: { target_family = "wasm" }, + emuscripten_platform: { target_os = "emscripten" }, + macos_platform: { target_os = "macos" }, + ios_platform: { target_os = "ios" }, + apple: { any(ios_platform, macos_platform) }, + free_unix: { all(unix, not(apple), not(android_platform), not(ohos_platform)) }, + + native: { not(wasm_platform) }, send_sync: { any( - not(target_arch = "wasm32"), + not(wasm_platform), all(feature = "fragile-send-sync-non-atomic-wasm", not(target_feature = "atomics")) ) }, - webgl: { all(target_arch = "wasm32", not(target_os = "emscripten"), gles) }, - Emscripten: { all(target_os = "emscripten", gles) }, - dx12: { all(target_os = "windows", feature = "dx12") }, + webgl: { all(wasm_platform, not(emuscripten_platform), gles) }, + Emscripten: { all(emuscripten_platform, gles) }, + dx12: { all(windows, feature = "dx12") }, gles: { all(feature = "gles") }, // Within the GL ES backend, use `std` and be Send + Sync only if we are using a target // that, among the ones where the GL ES backend is supported, has `std`. gles_with_std: { all( feature = "gles", any( - not(target_arch = "wasm32"), + not(wasm_platform), // Accept wasm32-unknown-unknown, which uniquely has a stub `std` all(target_vendor = "unknown", target_os = "unknown"), // Accept wasm32-unknown-emscripten and similar, which has a real `std` - target_os = "emscripten" + emuscripten_platform ) ) }, - metal: { all(target_vendor = "apple", feature = "metal") }, - vulkan: { all(not(target_arch = "wasm32"), feature = "vulkan") }, + // GLES_Backends. + gles_egl_backend: { all(feature = "gles", any(windows, unix), not(apple), not(wasm_platform)) }, + // Not Support GLX, TODO: Add support for glx + // gles_glx_backend: { all(feature = "gles", feature = "glx", x11_platform, not(wasm_platform)) }, + gles_wgl_backend: { all(feature = "gles", windows, not(wasm_platform)) }, + gles_cgl_backend: { all(feature = "gles", macos_platform, not(wasm_platform)) }, + + metal: { all(apple, feature = "metal") }, + vulkan: { all(not(wasm_platform), feature = "vulkan") }, any_backend: { any(dx12, metal, vulkan, gles) }, // ⚠️ Keep in sync with target.cfg() definition in Cargo.toml and cfg_alias in `wgpu` crate ⚠️ - static_dxc: { all(target_os = "windows", feature = "static-dxc", not(target_arch = "aarch64"), target_env = "msvc") }, + static_dxc: { all(windows, feature = "static-dxc", not(target_arch = "aarch64"), target_env = "msvc") }, supports_64bit_atomics: { target_has_atomic = "64" }, supports_ptr_atomics: { target_has_atomic = "ptr" } } diff --git a/wgpu-hal/examples/halmark/main.rs b/wgpu-hal/examples/halmark/main.rs index cc6f51dfa3..c39bbc97a5 100644 --- a/wgpu-hal/examples/halmark/main.rs +++ b/wgpu-hal/examples/halmark/main.rs @@ -95,6 +95,7 @@ impl Example { fn init(window: &winit::window::Window) -> Result> { // The Instance can be initialized with the DisplayHandle from the EventLoop as well let raw_display_handle = window.display_handle()?; + let raw_window_handle = window.window_handle()?; let instance_desc = hal::InstanceDescriptor { name: "example", @@ -104,6 +105,7 @@ impl Example { backend_options: wgpu_types::BackendOptions::default(), telemetry: None, display: Some(raw_display_handle), + window: Some(raw_window_handle), }; let instance = unsafe { A::Instance::init(&instance_desc)? }; let surface = { diff --git a/wgpu-hal/examples/ray-traced-triangle/main.rs b/wgpu-hal/examples/ray-traced-triangle/main.rs index 2b835c49ea..0e1c463f40 100644 --- a/wgpu-hal/examples/ray-traced-triangle/main.rs +++ b/wgpu-hal/examples/ray-traced-triangle/main.rs @@ -237,6 +237,7 @@ impl Example { // The Instance can be initialized with the DisplayHandle from the EventLoop as well let raw_display_handle = window.display_handle()?; + let raw_window_handle = window.window_handle()?; let instance_desc = hal::InstanceDescriptor { name: "example", @@ -251,6 +252,7 @@ impl Example { }, telemetry: None, display: Some(raw_display_handle), + window: Some(raw_window_handle), }; let instance = unsafe { A::Instance::init(&instance_desc)? }; let surface = { diff --git a/wgpu-hal/src/gles/egl.rs b/wgpu-hal/src/gles/egl.rs deleted file mode 100644 index 606d1b2d4a..0000000000 --- a/wgpu-hal/src/gles/egl.rs +++ /dev/null @@ -1,1440 +0,0 @@ -use alloc::{string::String, sync::Arc, vec::Vec}; -use core::{ffi, mem::ManuallyDrop, ptr, time::Duration}; -use std::sync::LazyLock; - -use glow::HasContext; -use hashbrown::HashMap; -use parking_lot::{MappedMutexGuard, Mutex, MutexGuard, RwLock}; - -/// The amount of time to wait while trying to obtain a lock to the adapter context -const CONTEXT_LOCK_TIMEOUT_SECS: u64 = 6; - -const EGL_CONTEXT_FLAGS_KHR: i32 = 0x30FC; -const EGL_CONTEXT_OPENGL_DEBUG_BIT_KHR: i32 = 0x0001; -const EGL_CONTEXT_OPENGL_ROBUST_ACCESS_EXT: i32 = 0x30BF; -const EGL_PLATFORM_WAYLAND_KHR: u32 = 0x31D8; -const EGL_PLATFORM_X11_KHR: u32 = 0x31D5; -const EGL_PLATFORM_ANGLE_ANGLE: u32 = 0x3202; -const EGL_PLATFORM_ANGLE_NATIVE_PLATFORM_TYPE_ANGLE: u32 = 0x348F; -const EGL_PLATFORM_ANGLE_DEBUG_LAYERS_ENABLED: u32 = 0x3451; -const EGL_PLATFORM_SURFACELESS_MESA: u32 = 0x31DD; -const EGL_GL_COLORSPACE_KHR: u32 = 0x309D; -const EGL_GL_COLORSPACE_SRGB_KHR: u32 = 0x3089; - -#[cfg(not(Emscripten))] -type EglInstance = khronos_egl::DynamicInstance; - -#[cfg(Emscripten)] -type EglInstance = khronos_egl::Instance; - -type EglLabel = *const ffi::c_void; - -#[allow(clippy::upper_case_acronyms)] -type EGLDEBUGPROCKHR = Option< - unsafe extern "system" fn( - error: khronos_egl::Enum, - command: *const ffi::c_char, - message_type: u32, - thread_label: EglLabel, - object_label: EglLabel, - message: *const ffi::c_char, - ), ->; - -const EGL_DEBUG_MSG_CRITICAL_KHR: u32 = 0x33B9; -const EGL_DEBUG_MSG_ERROR_KHR: u32 = 0x33BA; -const EGL_DEBUG_MSG_WARN_KHR: u32 = 0x33BB; -const EGL_DEBUG_MSG_INFO_KHR: u32 = 0x33BC; - -type EglDebugMessageControlFun = unsafe extern "system" fn( - proc: EGLDEBUGPROCKHR, - attrib_list: *const khronos_egl::Attrib, -) -> ffi::c_int; - -unsafe extern "system" fn egl_debug_proc( - error: khronos_egl::Enum, - command_raw: *const ffi::c_char, - message_type: u32, - _thread_label: EglLabel, - _object_label: EglLabel, - message_raw: *const ffi::c_char, -) { - let log_severity = match message_type { - EGL_DEBUG_MSG_CRITICAL_KHR | EGL_DEBUG_MSG_ERROR_KHR => log::Level::Error, - EGL_DEBUG_MSG_WARN_KHR => log::Level::Warn, - // We intentionally suppress info messages down to debug - // so that users are not inundated with info messages from - // the runtime. - EGL_DEBUG_MSG_INFO_KHR => log::Level::Debug, - _ => log::Level::Trace, - }; - let command = unsafe { ffi::CStr::from_ptr(command_raw) }.to_string_lossy(); - let message = if message_raw.is_null() { - "".into() - } else { - unsafe { ffi::CStr::from_ptr(message_raw) }.to_string_lossy() - }; - - log::log!(log_severity, "EGL '{command}' code 0x{error:x}: {message}",); -} - -#[derive(Clone, Copy, Debug)] -enum SrgbFrameBufferKind { - /// No support for SRGB surface - None, - /// Using EGL 1.5's support for colorspaces - Core, - /// Using EGL_KHR_gl_colorspace - Khr, -} - -/// Choose GLES framebuffer configuration. -fn choose_config( - egl: &EglInstance, - display: khronos_egl::Display, - srgb_kind: SrgbFrameBufferKind, -) -> Result<(khronos_egl::Config, bool), crate::InstanceError> { - //TODO: EGL_SLOW_CONFIG - let tiers = [ - ( - "off-screen", - &[ - khronos_egl::SURFACE_TYPE, - khronos_egl::PBUFFER_BIT, - khronos_egl::RENDERABLE_TYPE, - khronos_egl::OPENGL_ES2_BIT, - ][..], - ), - ( - "presentation", - &[khronos_egl::SURFACE_TYPE, khronos_egl::WINDOW_BIT][..], - ), - #[cfg(not(target_os = "android"))] - ( - "native-render", - &[khronos_egl::NATIVE_RENDERABLE, khronos_egl::TRUE as _][..], - ), - ]; - - let mut attributes = Vec::with_capacity(9); - for tier_max in (0..tiers.len()).rev() { - let name = tiers[tier_max].0; - log::debug!("\tTrying {name}"); - - attributes.clear(); - for &(_, tier_attr) in tiers[..=tier_max].iter() { - attributes.extend_from_slice(tier_attr); - } - // make sure the Alpha is enough to support sRGB - match srgb_kind { - SrgbFrameBufferKind::None => {} - _ => { - attributes.push(khronos_egl::ALPHA_SIZE); - attributes.push(8); - } - } - attributes.push(khronos_egl::NONE); - - match egl.choose_first_config(display, &attributes) { - Ok(Some(config)) => { - if tier_max == 1 { - //Note: this has been confirmed to malfunction on Intel+NV laptops, - // but also on Angle. - log::info!("EGL says it can present to the window but not natively",); - } - // Android emulator can't natively present either. - let tier_threshold = - if cfg!(target_os = "android") || cfg!(windows) || cfg!(target_env = "ohos") { - 1 - } else { - 2 - }; - return Ok((config, tier_max >= tier_threshold)); - } - Ok(None) => { - log::debug!("No config found!"); - } - Err(e) => { - log::error!("error in choose_first_config: {e:?}"); - } - } - } - - // TODO: include diagnostic details that are currently logged - Err(crate::InstanceError::new(String::from( - "unable to find an acceptable EGL framebuffer configuration", - ))) -} - -#[derive(Clone, Debug)] -struct EglContext { - instance: Arc, - version: (i32, i32), - display: khronos_egl::Display, - raw: khronos_egl::Context, - pbuffer: Option, -} - -impl EglContext { - fn make_current(&self) { - self.instance - .make_current(self.display, self.pbuffer, self.pbuffer, Some(self.raw)) - .unwrap(); - } - - fn unmake_current(&self) { - self.instance - .make_current(self.display, None, None, None) - .unwrap(); - } -} - -/// A wrapper around a [`glow::Context`] and the required EGL context that uses locking to guarantee -/// exclusive access when shared with multiple threads. -pub struct AdapterContext { - glow: Mutex>, - egl: Option, -} - -unsafe impl Sync for AdapterContext {} -unsafe impl Send for AdapterContext {} - -impl AdapterContext { - pub fn is_owned(&self) -> bool { - self.egl.is_some() - } - - /// Returns the EGL instance. - /// - /// This provides access to EGL functions and the ability to load GL and EGL extension functions. - pub fn egl_instance(&self) -> Option<&EglInstance> { - self.egl.as_ref().map(|egl| &*egl.instance) - } - - /// Returns the EGLDisplay corresponding to the adapter context. - /// - /// Returns [`None`] if the adapter was externally created. - pub fn raw_display(&self) -> Option<&khronos_egl::Display> { - self.egl.as_ref().map(|egl| &egl.display) - } - - /// Returns the EGL version the adapter context was created with. - /// - /// Returns [`None`] if the adapter was externally created. - pub fn egl_version(&self) -> Option<(i32, i32)> { - self.egl.as_ref().map(|egl| egl.version) - } - - pub fn raw_context(&self) -> *mut ffi::c_void { - match self.egl { - Some(ref egl) => egl.raw.as_ptr(), - None => ptr::null_mut(), - } - } -} - -impl Drop for AdapterContext { - fn drop(&mut self) { - struct CurrentGuard<'a>(&'a EglContext); - impl Drop for CurrentGuard<'_> { - fn drop(&mut self) { - self.0.unmake_current(); - } - } - - // Context must be current when dropped. See safety docs on - // `glow::HasContext`. - // - // 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.egl.as_ref().map(|egl| { - egl.make_current(); - CurrentGuard(egl) - }); - let glow = self.glow.get_mut(); - // SAFETY: Field not used after this. - unsafe { ManuallyDrop::drop(glow) }; - } -} - -struct EglContextLock<'a> { - instance: &'a Arc, - display: khronos_egl::Display, -} - -/// 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>, -} - -impl<'a> core::ops::Deref for AdapterContextLock<'a> { - type Target = glow::Context; - - fn deref(&self) -> &Self::Target { - &self.glow - } -} - -impl<'a> Drop for AdapterContextLock<'a> { - fn drop(&mut self) { - if let Some(egl) = self.egl.take() { - if let Err(err) = egl.instance.make_current(egl.display, None, None, None) { - log::error!("Failed to make EGL context current: {err:?}"); - } - } - } -} - -impl AdapterContext { - /// Get's the [`glow::Context`] without waiting for a lock - /// - /// # Safety - /// - /// This should only be called when you have manually made sure that the current thread has made - /// the EGL context current and that no other thread also has the EGL context current. - /// Additionally, you must manually make the EGL context **not** current after you are done with - /// it, so that future calls to `lock()` will not fail. - /// - /// > **Note:** Calling this function **will** still lock the [`glow::Context`] which adds an - /// > extra safe-guard against accidental concurrent access to the context. - pub unsafe fn get_without_egl_lock(&self) -> MappedMutexGuard<'_, glow::Context> { - let guard = self - .glow - .try_lock_for(Duration::from_secs(CONTEXT_LOCK_TIMEOUT_SECS)) - .expect("Could not lock adapter context. This is most-likely a deadlock."); - MutexGuard::map(guard, |glow| &mut **glow) - } - - /// Obtain a lock to the EGL context and get handle to the [`glow::Context`] that can be used to - /// do rendering. - #[track_caller] - pub fn lock<'a>(&'a self) -> AdapterContextLock<'a> { - let glow = self - .glow - // Don't lock forever. If it takes longer than 1 second to get the lock we've got a - // deadlock and should panic to show where we got stuck - .try_lock_for(Duration::from_secs(CONTEXT_LOCK_TIMEOUT_SECS)) - .expect("Could not lock adapter context. This is most-likely a deadlock."); - - let egl = self.egl.as_ref().map(|egl| { - egl.make_current(); - EglContextLock { - instance: &egl.instance, - display: egl.display, - } - }); - - AdapterContextLock { glow, egl } - } -} - -#[derive(Debug)] -struct Inner { - /// Note: the context contains a dummy pbuffer (1x1). - /// Required for `eglMakeCurrent` on platforms that doesn't supports `EGL_KHR_surfaceless_context`. - egl: EglContext, - #[allow(unused)] - version: (i32, i32), - supports_native_window: bool, - config: khronos_egl::Config, - /// Method by which the framebuffer should support srgb - srgb_kind: SrgbFrameBufferKind, -} - -// Different calls to `eglGetPlatformDisplay` may return the same `Display`, making it a global -// state of all our `EglContext`s. This forces us to track the number of such context to prevent -// terminating the display if it's currently used by another `EglContext`. -static DISPLAYS_REFERENCE_COUNT: LazyLock>> = - LazyLock::new(Default::default); - -fn initialize_display( - egl: &EglInstance, - display: khronos_egl::Display, -) -> Result<(i32, i32), khronos_egl::Error> { - let mut guard = DISPLAYS_REFERENCE_COUNT.lock(); - *guard.entry(display.as_ptr() as usize).or_default() += 1; - - // We don't need to check the reference count here since according to the `eglInitialize` - // documentation, initializing an already initialized EGL display connection has no effect - // besides returning the version numbers. - egl.initialize(display) -} - -fn terminate_display( - egl: &EglInstance, - display: khronos_egl::Display, -) -> Result<(), khronos_egl::Error> { - let key = &(display.as_ptr() as usize); - let mut guard = DISPLAYS_REFERENCE_COUNT.lock(); - let count_ref = guard - .get_mut(key) - .expect("Attempted to decref a display before incref was called"); - - if *count_ref > 1 { - *count_ref -= 1; - - Ok(()) - } else { - guard.remove(key); - - egl.terminate(display) - } -} - -fn instance_err( - message: impl Into, -) -> impl FnOnce(E) -> crate::InstanceError { - move |e| crate::InstanceError::with_source(message.into(), e) -} - -impl Inner { - fn create( - flags: wgt::InstanceFlags, - egl: Arc, - display: khronos_egl::Display, - force_gles_minor_version: wgt::Gles3MinorVersion, - ) -> Result { - let version = initialize_display(&egl, display) - .map_err(instance_err("failed to initialize EGL display connection"))?; - let vendor = egl - .query_string(Some(display), khronos_egl::VENDOR) - .map_err(instance_err("failed to query EGL vendor"))?; - let display_extensions = egl - .query_string(Some(display), khronos_egl::EXTENSIONS) - .map_err(instance_err("failed to query EGL display extensions"))? - .to_string_lossy(); - log::debug!("Display vendor {vendor:?}, version {version:?}",); - log::debug!( - "Display extensions: {:#?}", - display_extensions.split_whitespace().collect::>() - ); - - let srgb_kind = if version >= (1, 5) { - log::debug!("\tEGL surface: +srgb"); - SrgbFrameBufferKind::Core - } else if display_extensions.contains("EGL_KHR_gl_colorspace") { - log::debug!("\tEGL surface: +srgb khr"); - SrgbFrameBufferKind::Khr - } else { - log::debug!("\tEGL surface: -srgb"); - SrgbFrameBufferKind::None - }; - - if log::max_level() >= log::LevelFilter::Trace { - log::trace!("Configurations:"); - let config_count = egl - .get_config_count(display) - .map_err(instance_err("failed to get config count"))?; - let mut configurations = Vec::with_capacity(config_count); - egl.get_configs(display, &mut configurations) - .map_err(instance_err("failed to get configs"))?; - for &config in configurations.iter() { - log::trace!("\tCONFORMANT=0x{:X?}, RENDERABLE=0x{:X?}, NATIVE_RENDERABLE=0x{:X?}, SURFACE_TYPE=0x{:X?}, ALPHA_SIZE={:?}", - egl.get_config_attrib(display, config, khronos_egl::CONFORMANT), - egl.get_config_attrib(display, config, khronos_egl::RENDERABLE_TYPE), - egl.get_config_attrib(display, config, khronos_egl::NATIVE_RENDERABLE), - egl.get_config_attrib(display, config, khronos_egl::SURFACE_TYPE), - egl.get_config_attrib(display, config, khronos_egl::ALPHA_SIZE), - ); - } - } - - let (config, supports_native_window) = choose_config(&egl, display, srgb_kind)?; - - let supports_opengl = if version >= (1, 4) { - let client_apis = egl - .query_string(Some(display), khronos_egl::CLIENT_APIS) - .map_err(instance_err("failed to query EGL client APIs string"))? - .to_string_lossy(); - client_apis - .split(' ') - .any(|client_api| client_api == "OpenGL") - } else { - false - }; - egl.bind_api(if supports_opengl { - khronos_egl::OPENGL_API - } else { - khronos_egl::OPENGL_ES_API - }) - .map_err(instance_err("failed to bind API"))?; - - let mut khr_context_flags = 0; - let supports_khr_context = display_extensions.contains("EGL_KHR_create_context"); - - let mut context_attributes = vec![]; - let mut gl_context_attributes = vec![]; - let mut gles_context_attributes = vec![]; - gl_context_attributes.push(khronos_egl::CONTEXT_MAJOR_VERSION); - gl_context_attributes.push(3); - gl_context_attributes.push(khronos_egl::CONTEXT_MINOR_VERSION); - gl_context_attributes.push(3); - if supports_opengl && force_gles_minor_version != wgt::Gles3MinorVersion::Automatic { - log::warn!("Ignoring specified GLES minor version as OpenGL is used"); - } - gles_context_attributes.push(khronos_egl::CONTEXT_MAJOR_VERSION); - gles_context_attributes.push(3); // Request GLES 3.0 or higher - if force_gles_minor_version != wgt::Gles3MinorVersion::Automatic { - gles_context_attributes.push(khronos_egl::CONTEXT_MINOR_VERSION); - gles_context_attributes.push(match force_gles_minor_version { - wgt::Gles3MinorVersion::Automatic => unreachable!(), - wgt::Gles3MinorVersion::Version0 => 0, - wgt::Gles3MinorVersion::Version1 => 1, - wgt::Gles3MinorVersion::Version2 => 2, - }); - } - if flags.contains(wgt::InstanceFlags::DEBUG) { - if version >= (1, 5) { - log::debug!("\tEGL context: +debug"); - context_attributes.push(khronos_egl::CONTEXT_OPENGL_DEBUG); - context_attributes.push(khronos_egl::TRUE as _); - } else if supports_khr_context { - log::debug!("\tEGL context: +debug KHR"); - khr_context_flags |= EGL_CONTEXT_OPENGL_DEBUG_BIT_KHR; - } else { - log::debug!("\tEGL context: -debug"); - } - } - - if khr_context_flags != 0 { - context_attributes.push(EGL_CONTEXT_FLAGS_KHR); - context_attributes.push(khr_context_flags); - } - - gl_context_attributes.extend(&context_attributes); - gles_context_attributes.extend(&context_attributes); - - let context = { - enum Robustness { - Core, - Ext, - } - - let mut robustness = if version >= (1, 5) { - Some(Robustness::Core) - } else if display_extensions.contains("EGL_EXT_create_context_robustness") { - Some(Robustness::Ext) - } else { - None - }; - - loop { - let robustness_attributes = match robustness { - Some(Robustness::Core) => { - vec![ - khronos_egl::CONTEXT_OPENGL_ROBUST_ACCESS, - khronos_egl::TRUE as _, - khronos_egl::NONE, - ] - } - Some(Robustness::Ext) => { - vec![ - EGL_CONTEXT_OPENGL_ROBUST_ACCESS_EXT, - khronos_egl::TRUE as _, - khronos_egl::NONE, - ] - } - None => vec![khronos_egl::NONE], - }; - - let mut gl_context_attributes = gl_context_attributes.clone(); - gl_context_attributes.extend(&robustness_attributes); - - let mut gles_context_attributes = gles_context_attributes.clone(); - gles_context_attributes.extend(&robustness_attributes); - - let result = if supports_opengl { - egl.create_context(display, config, None, &gl_context_attributes) - .or_else(|_| { - egl.bind_api(khronos_egl::OPENGL_ES_API)?; - egl.create_context(display, config, None, &gles_context_attributes) - }) - } else { - egl.create_context(display, config, None, &gles_context_attributes) - }; - - match (result, robustness) { - // We have a context at the requested robustness level - (Ok(_), robustness) => { - match robustness { - Some(Robustness::Core) => { - log::debug!("\tEGL context: +robust access"); - } - Some(Robustness::Ext) => { - log::debug!("\tEGL context: +robust access EXT"); - } - None => { - log::debug!("\tEGL context: -robust access"); - } - } - - break result; - } - - // BadAttribute could mean that context creation is not supported at the requested robustness level - // We try the next robustness level. - (Err(khronos_egl::Error::BadAttribute), Some(r)) => { - // Trying EXT robustness if Core robustness is not working - // and EXT robustness is supported. - robustness = if matches!(r, Robustness::Core) - && display_extensions.contains("EGL_EXT_create_context_robustness") - { - Some(Robustness::Ext) - } else { - None - }; - - continue; - } - - // Any other error, or depleted robustness levels, we give up. - _ => break result, - } - } - .map_err(|e| { - crate::InstanceError::with_source( - String::from("unable to create OpenGL or GLES 3.x context"), - e, - ) - }) - }?; - - // Testing if context can be binded without surface - // and creating dummy pbuffer surface if not. - let pbuffer = if version >= (1, 5) - || display_extensions.contains("EGL_KHR_surfaceless_context") - || cfg!(Emscripten) - { - log::debug!("\tEGL context: +surfaceless"); - None - } else { - let attributes = [ - khronos_egl::WIDTH, - 1, - khronos_egl::HEIGHT, - 1, - khronos_egl::NONE, - ]; - egl.create_pbuffer_surface(display, config, &attributes) - .map(Some) - .map_err(|e| { - crate::InstanceError::with_source( - String::from("error in create_pbuffer_surface"), - e, - ) - })? - }; - - Ok(Self { - egl: EglContext { - instance: egl, - display, - raw: context, - pbuffer, - version, - }, - version, - supports_native_window, - config, - srgb_kind, - }) - } -} - -impl Drop for Inner { - fn drop(&mut self) { - // ERROR: Since EglContext is erroneously Clone, these handles could be copied and - // accidentally used elsewhere outside of Inner, despite us assuming ownership and - // destroying the handles here. - if let Err(e) = self - .egl - .instance - .destroy_context(self.egl.display, self.egl.raw) - { - log::warn!("Error in destroy_context: {e:?}"); - } - - if let Err(e) = terminate_display(&self.egl.instance, self.egl.display) { - log::warn!("Error in terminate: {e:?}"); - } - } -} - -#[derive(Clone, Copy, Debug, PartialEq)] -enum WindowKind { - Wayland, - X11, - AngleX11, - Unknown, -} - -#[derive(Clone, Debug)] -struct WindowSystemInterface { - kind: WindowKind, -} - -pub struct Instance { - wsi: WindowSystemInterface, - flags: wgt::InstanceFlags, - options: wgt::GlBackendOptions, - inner: Mutex, -} - -impl Instance { - pub fn raw_display(&self) -> khronos_egl::Display { - self.inner - .try_lock() - .expect("Could not lock instance. This is most-likely a deadlock.") - .egl - .display - } - - /// Returns the version of the EGL display. - pub fn egl_version(&self) -> (i32, i32) { - self.inner - .try_lock() - .expect("Could not lock instance. This is most-likely a deadlock.") - .version - } - - pub fn egl_config(&self) -> khronos_egl::Config { - self.inner - .try_lock() - .expect("Could not lock instance. This is most-likely a deadlock.") - .config - } -} - -unsafe impl Send for Instance {} -unsafe impl Sync for Instance {} - -impl crate::Instance for Instance { - type A = super::Api; - - unsafe fn init(desc: &crate::InstanceDescriptor<'_>) -> Result { - use raw_window_handle::RawDisplayHandle as Rdh; - - profiling::scope!("Init OpenGL (EGL) Backend"); - #[cfg(Emscripten)] - let egl_result: Result = - Ok(khronos_egl::Instance::new(khronos_egl::Static)); - - #[cfg(not(Emscripten))] - let egl_result = if cfg!(windows) { - unsafe { - khronos_egl::DynamicInstance::::load_required_from_filename( - "libEGL.dll", - ) - } - } else if cfg!(target_vendor = "apple") { - unsafe { - khronos_egl::DynamicInstance::::load_required_from_filename( - "libEGL.dylib", - ) - } - } else { - unsafe { khronos_egl::DynamicInstance::::load_required() } - }; - let egl = egl_result - .map(Arc::new) - .map_err(instance_err("unable to open libEGL"))?; - - let client_extensions = egl.query_string(None, khronos_egl::EXTENSIONS); - - let client_ext_str = match client_extensions { - Ok(ext) => ext.to_string_lossy().into_owned(), - Err(_) => String::new(), - }; - log::debug!( - "Client extensions: {:#?}", - client_ext_str.split_whitespace().collect::>() - ); - - #[cfg(not(Emscripten))] - let egl1_5 = egl.upcast::(); - - #[cfg(Emscripten)] - let egl1_5: Option<&Arc> = Some(&egl); - - let (display, wsi_kind) = match (desc.display.map(|d| d.as_raw()), egl1_5) { - (Some(Rdh::Wayland(wayland_display_handle)), Some(egl)) - if client_ext_str.contains("EGL_EXT_platform_wayland") => - { - log::debug!("Using Wayland platform"); - let display_attributes = [khronos_egl::ATTRIB_NONE]; - let display = unsafe { - egl.get_platform_display( - EGL_PLATFORM_WAYLAND_KHR, - wayland_display_handle.display.as_ptr(), - &display_attributes, - ) - } - .map_err(instance_err("failed to get Wayland display"))?; - (display, WindowKind::Wayland) - } - (Some(Rdh::Xlib(xlib_display_handle)), Some(egl)) - if client_ext_str.contains("EGL_EXT_platform_x11") => - { - log::debug!("Using X11 platform"); - let display_attributes = [khronos_egl::ATTRIB_NONE]; - let display = unsafe { - egl.get_platform_display( - EGL_PLATFORM_X11_KHR, - xlib_display_handle - .display - .map_or(khronos_egl::DEFAULT_DISPLAY, ptr::NonNull::as_ptr), - &display_attributes, - ) - } - .map_err(instance_err("failed to get X11 display"))?; - (display, WindowKind::X11) - } - (Some(Rdh::Xlib(xlib_display_handle)), Some(egl)) - if client_ext_str.contains("EGL_ANGLE_platform_angle") => - { - log::debug!("Using Angle platform with X11"); - let display_attributes = [ - EGL_PLATFORM_ANGLE_NATIVE_PLATFORM_TYPE_ANGLE as khronos_egl::Attrib, - EGL_PLATFORM_X11_KHR as khronos_egl::Attrib, - EGL_PLATFORM_ANGLE_DEBUG_LAYERS_ENABLED as khronos_egl::Attrib, - usize::from(desc.flags.contains(wgt::InstanceFlags::VALIDATION)), - khronos_egl::ATTRIB_NONE, - ]; - let display = unsafe { - egl.get_platform_display( - EGL_PLATFORM_ANGLE_ANGLE, - xlib_display_handle - .display - .map_or(khronos_egl::DEFAULT_DISPLAY, ptr::NonNull::as_ptr), - &display_attributes, - ) - } - .map_err(instance_err("failed to get Angle display"))?; - (display, WindowKind::AngleX11) - } - (Some(Rdh::Xcb(_xcb_display_handle)), Some(_egl)) => todo!("xcb"), - x if client_ext_str.contains("EGL_MESA_platform_surfaceless") => { - log::debug!( - "No (or unknown) windowing system ({x:?}) present. Using surfaceless platform" - ); - #[allow(clippy::unnecessary_literal_unwrap)] - // This is only a literal on Emscripten - // TODO: This extension is also supported on EGL 1.4 with EGL_EXT_platform_base: https://registry.khronos.org/EGL/extensions/MESA/EGL_MESA_platform_surfaceless.txt - let egl = egl1_5.expect("Failed to get EGL 1.5 for surfaceless"); - let display = unsafe { - egl.get_platform_display( - EGL_PLATFORM_SURFACELESS_MESA, - khronos_egl::DEFAULT_DISPLAY, - &[khronos_egl::ATTRIB_NONE], - ) - } - .map_err(instance_err("failed to get MESA surfaceless display"))?; - (display, WindowKind::Unknown) - } - x => { - log::debug!( - "No (or unknown) windowing system {x:?} and EGL_MESA_platform_surfaceless not available. Using default platform" - ); - let display = - unsafe { egl.get_display(khronos_egl::DEFAULT_DISPLAY) }.ok_or_else(|| { - crate::InstanceError::new("Failed to get default display".into()) - })?; - (display, WindowKind::Unknown) - } - }; - - if desc.flags.contains(wgt::InstanceFlags::VALIDATION) - && client_ext_str.contains("EGL_KHR_debug") - { - log::debug!("Enabling EGL debug output"); - let function: EglDebugMessageControlFun = { - let addr = egl - .get_proc_address("eglDebugMessageControlKHR") - .ok_or_else(|| { - crate::InstanceError::new( - "failed to get `eglDebugMessageControlKHR` proc address".into(), - ) - })?; - unsafe { core::mem::transmute(addr) } - }; - let attributes = [ - EGL_DEBUG_MSG_CRITICAL_KHR as khronos_egl::Attrib, - 1, - EGL_DEBUG_MSG_ERROR_KHR as khronos_egl::Attrib, - 1, - EGL_DEBUG_MSG_WARN_KHR as khronos_egl::Attrib, - 1, - EGL_DEBUG_MSG_INFO_KHR as khronos_egl::Attrib, - 1, - khronos_egl::ATTRIB_NONE, - ]; - unsafe { (function)(Some(egl_debug_proc), attributes.as_ptr()) }; - } - - let inner = Inner::create( - desc.flags, - egl, - display, - desc.backend_options.gl.gles_minor_version, - )?; - - Ok(Instance { - wsi: WindowSystemInterface { kind: wsi_kind }, - flags: desc.flags, - options: desc.backend_options.gl.clone(), - inner: Mutex::new(inner), - }) - } - - #[cfg_attr(target_os = "macos", allow(unused, unused_mut, unreachable_code))] - unsafe fn create_surface( - &self, - display_handle: raw_window_handle::RawDisplayHandle, - window_handle: raw_window_handle::RawWindowHandle, - ) -> Result { - use raw_window_handle::RawWindowHandle as Rwh; - - let inner = self.inner.lock(); - - match (window_handle, display_handle) { - (Rwh::Xlib(_), _) => {} - (Rwh::Xcb(_), _) => {} - (Rwh::Win32(_), _) => {} - (Rwh::AppKit(_), _) => {} - (Rwh::OhosNdk(_), _) => {} - #[cfg(target_os = "android")] - (Rwh::AndroidNdk(handle), _) => { - let format = inner - .egl - .instance - .get_config_attrib( - inner.egl.display, - inner.config, - khronos_egl::NATIVE_VISUAL_ID, - ) - .map_err(instance_err("failed to get config NATIVE_VISUAL_ID"))?; - - let ret = unsafe { - ndk_sys::ANativeWindow_setBuffersGeometry( - handle - .a_native_window - .as_ptr() - .cast::(), - 0, - 0, - format, - ) - }; - - if ret != 0 { - return Err(crate::InstanceError::new(format!( - "error {ret} returned from ANativeWindow_setBuffersGeometry", - ))); - } - } - (Rwh::Wayland(_), _) => {} - #[cfg(Emscripten)] - (Rwh::Web(_), _) => {} - other => { - return Err(crate::InstanceError::new(format!( - "unsupported window: {other:?}" - ))); - } - }; - - inner.egl.unmake_current(); - - Ok(Surface { - egl: inner.egl.clone(), - wsi: self.wsi.clone(), - config: inner.config, - presentable: inner.supports_native_window, - raw_window_handle: window_handle, - swapchain: RwLock::new(None), - srgb_kind: inner.srgb_kind, - }) - } - - unsafe fn enumerate_adapters( - &self, - _surface_hint: Option<&Surface>, - ) -> Vec> { - let inner = self.inner.lock(); - inner.egl.make_current(); - - let mut gl = unsafe { - glow::Context::from_loader_function(|name| { - inner - .egl - .instance - .get_proc_address(name) - .map_or(ptr::null(), |p| p as *const _) - }) - }; - - // In contrast to OpenGL ES, OpenGL requires explicitly enabling sRGB conversions, - // as otherwise the user has to do the sRGB conversion. - if !matches!(inner.srgb_kind, SrgbFrameBufferKind::None) { - unsafe { gl.enable(glow::FRAMEBUFFER_SRGB) }; - } - - if self.flags.contains(wgt::InstanceFlags::DEBUG) && gl.supports_debug() { - log::debug!("Max label length: {}", unsafe { - gl.get_parameter_i32(glow::MAX_LABEL_LENGTH) - }); - } - - if self.flags.contains(wgt::InstanceFlags::VALIDATION) && gl.supports_debug() { - log::debug!("Enabling GLES debug output"); - unsafe { gl.enable(glow::DEBUG_OUTPUT) }; - unsafe { gl.debug_message_callback(super::gl_debug_message_callback) }; - } - - // 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(); - - unsafe { - super::Adapter::expose( - AdapterContext { - glow: Mutex::new(gl), - // ERROR: Copying owned reference handles here, be careful to not drop them! - egl: Some(inner.egl.clone()), - }, - self.options.clone(), - ) - } - .into_iter() - .collect() - } -} - -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 ffi::c_void, - options: wgt::GlBackendOptions, - ) -> Option> { - let context = unsafe { glow::Context::from_loader_function(fun) }; - unsafe { - Self::expose( - AdapterContext { - glow: Mutex::new(ManuallyDrop::new(context)), - egl: None, - }, - options, - ) - } - } - - pub fn adapter_context(&self) -> &AdapterContext { - &self.shared.context - } -} - -impl super::Device { - /// Returns the underlying EGL context. - pub fn context(&self) -> &AdapterContext { - &self.shared.context - } -} - -#[derive(Debug)] -pub struct Swapchain { - surface: khronos_egl::Surface, - wl_window: Option<*mut wayland_sys::egl::wl_egl_window>, - framebuffer: glow::Framebuffer, - renderbuffer: glow::Renderbuffer, - /// Extent because the window lies - extent: wgt::Extent3d, - format: wgt::TextureFormat, - format_desc: super::TextureFormatDesc, - #[allow(unused)] - sample_type: wgt::TextureSampleType, -} - -#[derive(Debug)] -pub struct Surface { - egl: EglContext, - wsi: WindowSystemInterface, - config: khronos_egl::Config, - pub(super) presentable: bool, - raw_window_handle: raw_window_handle::RawWindowHandle, - swapchain: RwLock>, - srgb_kind: SrgbFrameBufferKind, -} - -unsafe impl Send for Surface {} -unsafe impl Sync for Surface {} - -impl Surface { - pub(super) unsafe fn present( - &self, - _suf_texture: super::Texture, - context: &AdapterContext, - ) -> Result<(), crate::SurfaceError> { - let gl = unsafe { context.get_without_egl_lock() }; - let swapchain = self.swapchain.read(); - let sc = swapchain.as_ref().ok_or(crate::SurfaceError::Other( - "Surface has no swap-chain configured", - ))?; - - self.egl - .instance - .make_current( - self.egl.display, - Some(sc.surface), - Some(sc.surface), - Some(self.egl.raw), - ) - .map_err(|e| { - log::error!("make_current(surface) failed: {e}"); - crate::SurfaceError::Lost - })?; - - unsafe { gl.disable(glow::SCISSOR_TEST) }; - unsafe { gl.color_mask(true, true, true, true) }; - - unsafe { gl.bind_framebuffer(glow::DRAW_FRAMEBUFFER, None) }; - unsafe { gl.bind_framebuffer(glow::READ_FRAMEBUFFER, Some(sc.framebuffer)) }; - - if !matches!(self.srgb_kind, SrgbFrameBufferKind::None) { - // Disable sRGB conversions for `glBlitFramebuffer` as behavior does diverge between - // drivers and formats otherwise and we want to ensure no sRGB conversions happen. - unsafe { gl.disable(glow::FRAMEBUFFER_SRGB) }; - } - - // Note the Y-flipping here. GL's presentation is not flipped, - // but main rendering is. Therefore, we Y-flip the output positions - // in the shader, and also this blit. - unsafe { - gl.blit_framebuffer( - 0, - sc.extent.height as i32, - sc.extent.width as i32, - 0, - 0, - 0, - sc.extent.width as i32, - sc.extent.height as i32, - glow::COLOR_BUFFER_BIT, - glow::NEAREST, - ) - }; - - if !matches!(self.srgb_kind, SrgbFrameBufferKind::None) { - unsafe { gl.enable(glow::FRAMEBUFFER_SRGB) }; - } - - unsafe { gl.bind_framebuffer(glow::READ_FRAMEBUFFER, None) }; - - self.egl - .instance - .swap_buffers(self.egl.display, sc.surface) - .map_err(|e| { - log::error!("swap_buffers failed: {e}"); - crate::SurfaceError::Lost - // TODO: should we unset the current context here? - })?; - self.egl - .instance - .make_current(self.egl.display, None, None, None) - .map_err(|e| { - log::error!("make_current(null) failed: {e}"); - crate::SurfaceError::Lost - })?; - - Ok(()) - } - - unsafe fn unconfigure_impl( - &self, - device: &super::Device, - ) -> Option<( - khronos_egl::Surface, - Option<*mut wayland_sys::egl::wl_egl_window>, - )> { - let gl = &device.shared.context.lock(); - match self.swapchain.write().take() { - Some(sc) => { - unsafe { gl.delete_renderbuffer(sc.renderbuffer) }; - unsafe { gl.delete_framebuffer(sc.framebuffer) }; - Some((sc.surface, sc.wl_window)) - } - None => None, - } - } - - pub fn supports_srgb(&self) -> bool { - match self.srgb_kind { - SrgbFrameBufferKind::None => false, - _ => true, - } - } -} - -impl crate::Surface for Surface { - type A = super::Api; - - unsafe fn configure( - &self, - device: &super::Device, - config: &crate::SurfaceConfiguration, - ) -> Result<(), crate::SurfaceError> { - use raw_window_handle::RawWindowHandle as Rwh; - - let (surface, wl_window) = match unsafe { self.unconfigure_impl(device) } { - Some((sc, wl_window)) => { - if let Some(window) = wl_window { - wayland_sys::ffi_dispatch!( - wayland_sys::egl::wayland_egl_handle(), - wl_egl_window_resize, - window, - config.extent.width as i32, - config.extent.height as i32, - 0, - 0, - ); - } - - (sc, wl_window) - } - None => { - let mut wl_window = None; - let (mut temp_xlib_handle, mut temp_xcb_handle); - let native_window_ptr = match (self.wsi.kind, self.raw_window_handle) { - (WindowKind::Unknown | WindowKind::X11, Rwh::Xlib(handle)) => { - temp_xlib_handle = handle.window; - ptr::from_mut(&mut temp_xlib_handle).cast::() - } - (WindowKind::AngleX11, Rwh::Xlib(handle)) => handle.window as *mut ffi::c_void, - (WindowKind::Unknown | WindowKind::X11, Rwh::Xcb(handle)) => { - temp_xcb_handle = handle.window; - ptr::from_mut(&mut temp_xcb_handle).cast::() - } - (WindowKind::AngleX11, Rwh::Xcb(handle)) => { - handle.window.get() as *mut ffi::c_void - } - (WindowKind::Unknown, Rwh::AndroidNdk(handle)) => { - handle.a_native_window.as_ptr() - } - (WindowKind::Unknown, Rwh::OhosNdk(handle)) => handle.native_window.as_ptr(), - #[cfg(unix)] - (WindowKind::Wayland, Rwh::Wayland(handle)) => { - let window = wayland_sys::ffi_dispatch!( - wayland_sys::egl::wayland_egl_handle(), - wl_egl_window_create, - handle.surface.as_ptr().cast(), - config.extent.width as i32, - config.extent.height as i32, - ); - wl_window = Some(window); - window.cast() - } - #[cfg(Emscripten)] - (WindowKind::Unknown, Rwh::Web(handle)) => handle.id as *mut ffi::c_void, - (WindowKind::Unknown, Rwh::Win32(handle)) => { - handle.hwnd.get() as *mut ffi::c_void - } - (WindowKind::Unknown, Rwh::AppKit(handle)) => { - #[cfg(not(target_os = "macos"))] - let window_ptr = handle.ns_view.as_ptr(); - #[cfg(target_os = "macos")] - let window_ptr = { - use objc::{msg_send, runtime::Object, sel, sel_impl}; - // ns_view always have a layer and don't need to verify that it exists. - let layer: *mut Object = - msg_send![handle.ns_view.as_ptr().cast::(), layer]; - layer.cast::() - }; - window_ptr - } - _ => { - log::warn!( - "Initialized platform {:?} doesn't work with window {:?}", - self.wsi.kind, - self.raw_window_handle - ); - return Err(crate::SurfaceError::Other("incompatible window kind")); - } - }; - - let mut attributes = vec![ - khronos_egl::RENDER_BUFFER, - // We don't want any of the buffering done by the driver, because we - // manage a swapchain on our side. - // Some drivers just fail on surface creation seeing `EGL_SINGLE_BUFFER`. - if cfg!(any( - target_os = "android", - target_os = "macos", - target_env = "ohos" - )) || cfg!(windows) - || self.wsi.kind == WindowKind::AngleX11 - { - khronos_egl::BACK_BUFFER - } else { - khronos_egl::SINGLE_BUFFER - }, - ]; - if config.format.is_srgb() { - match self.srgb_kind { - SrgbFrameBufferKind::None => {} - SrgbFrameBufferKind::Core => { - attributes.push(khronos_egl::GL_COLORSPACE); - attributes.push(khronos_egl::GL_COLORSPACE_SRGB); - } - SrgbFrameBufferKind::Khr => { - attributes.push(EGL_GL_COLORSPACE_KHR as i32); - attributes.push(EGL_GL_COLORSPACE_SRGB_KHR as i32); - } - } - } - attributes.push(khronos_egl::ATTRIB_NONE as i32); - - #[cfg(not(Emscripten))] - let egl1_5 = self.egl.instance.upcast::(); - - #[cfg(Emscripten)] - let egl1_5: Option<&Arc> = Some(&self.egl.instance); - - // Careful, we can still be in 1.4 version even if `upcast` succeeds - let raw_result = match egl1_5 { - Some(egl) if self.wsi.kind != WindowKind::Unknown => { - let attributes_usize = attributes - .into_iter() - .map(|v| v as usize) - .collect::>(); - unsafe { - egl.create_platform_window_surface( - self.egl.display, - self.config, - native_window_ptr, - &attributes_usize, - ) - } - } - _ => unsafe { - self.egl.instance.create_window_surface( - self.egl.display, - self.config, - native_window_ptr, - Some(&attributes), - ) - }, - }; - - match raw_result { - Ok(raw) => (raw, wl_window), - Err(e) => { - log::warn!("Error in create_window_surface: {e:?}"); - return Err(crate::SurfaceError::Lost); - } - } - } - }; - - let format_desc = device.shared.describe_texture_format(config.format); - let gl = &device.shared.context.lock(); - let renderbuffer = unsafe { gl.create_renderbuffer() }.map_err(|error| { - log::error!("Internal swapchain renderbuffer creation failed: {error}"); - crate::DeviceError::OutOfMemory - })?; - unsafe { gl.bind_renderbuffer(glow::RENDERBUFFER, Some(renderbuffer)) }; - unsafe { - gl.renderbuffer_storage( - glow::RENDERBUFFER, - format_desc.internal, - config.extent.width as _, - config.extent.height as _, - ) - }; - let framebuffer = unsafe { gl.create_framebuffer() }.map_err(|error| { - log::error!("Internal swapchain framebuffer creation failed: {error}"); - crate::DeviceError::OutOfMemory - })?; - unsafe { gl.bind_framebuffer(glow::READ_FRAMEBUFFER, Some(framebuffer)) }; - unsafe { - gl.framebuffer_renderbuffer( - glow::READ_FRAMEBUFFER, - glow::COLOR_ATTACHMENT0, - glow::RENDERBUFFER, - Some(renderbuffer), - ) - }; - unsafe { gl.bind_renderbuffer(glow::RENDERBUFFER, None) }; - unsafe { gl.bind_framebuffer(glow::READ_FRAMEBUFFER, None) }; - - let mut swapchain = self.swapchain.write(); - *swapchain = Some(Swapchain { - surface, - wl_window, - renderbuffer, - framebuffer, - extent: config.extent, - format: config.format, - format_desc, - sample_type: wgt::TextureSampleType::Float { filterable: false }, - }); - - Ok(()) - } - - unsafe fn unconfigure(&self, device: &super::Device) { - if let Some((surface, wl_window)) = unsafe { self.unconfigure_impl(device) } { - self.egl - .instance - .destroy_surface(self.egl.display, surface) - .unwrap(); - if let Some(window) = wl_window { - wayland_sys::ffi_dispatch!( - wayland_sys::egl::wayland_egl_handle(), - wl_egl_window_destroy, - window, - ); - } - } - } - - unsafe fn acquire_texture( - &self, - _timeout_ms: Option, //TODO - _fence: &super::Fence, - ) -> Result>, crate::SurfaceError> { - let swapchain = self.swapchain.read(); - let sc = swapchain.as_ref().ok_or(crate::SurfaceError::Other( - "Surface has no swap-chain configured", - ))?; - let texture = super::Texture { - inner: super::TextureInner::Renderbuffer { - raw: sc.renderbuffer, - }, - drop_guard: None, - array_layer_count: 1, - mip_level_count: 1, - format: sc.format, - format_desc: sc.format_desc.clone(), - copy_size: crate::CopyExtent { - width: sc.extent.width, - height: sc.extent.height, - depth: 1, - }, - }; - Ok(Some(crate::AcquiredSurfaceTexture { - texture, - suboptimal: false, - })) - } - unsafe fn discard_texture(&self, _texture: super::Texture) {} -} diff --git a/wgpu-hal/src/gles/glutin.rs b/wgpu-hal/src/gles/glutin.rs new file mode 100644 index 0000000000..89b86e43e6 --- /dev/null +++ b/wgpu-hal/src/gles/glutin.rs @@ -0,0 +1,771 @@ +use alloc::{string::String, sync::Arc, vec::Vec}; +use core::{ffi, mem::ManuallyDrop, num::NonZeroU32, ptr, time::Duration}; +use glutin::{context::AsRawContext, prelude::*}; + +use glow::HasContext; +use parking_lot::{Mutex, MutexGuard, RwLock}; + +/// The amount of time to wait while trying to obtain a lock to the adapter context +const CONTEXT_LOCK_TIMEOUT_SECS: u64 = 6; + +struct GlutinContext { + current_context: Option, + not_current_context: Option, +} + +type GlutinSurface = glutin::surface::Surface; +type GlutinWindowSurfaceAttributesBuilder = + glutin::surface::SurfaceAttributesBuilder; +type GlutinWindowSurface = GlutinSurface; + +type GlutinDisplay = glutin::display::Display; + +impl GlutinContext { + fn make_current( + &mut self, + surface: &GlutinSurface, + ) -> Result<(), glutin::error::Error> { + if let Some(not_current) = self.not_current_context.take() { + let current = not_current.make_current(surface)?; + self.current_context = Some(current); + } + + Ok(()) + } + + fn unmake_current(&mut self) -> Result<(), glutin::error::Error> { + if let Some(current) = self.current_context.take() { + let not_current = current.make_not_current()?; + self.not_current_context = Some(not_current); + } + + Ok(()) + } + + fn raw_context(&self) -> *mut ffi::c_void { + let raw_context = if let Some(current) = &self.current_context { + current.raw_context() + } else if let Some(not_current) = &self.not_current_context { + not_current.raw_context() + } else { + return ptr::null_mut(); + }; + + match raw_context { + #[cfg(gles_egl_backend)] + glutin::context::RawContext::Egl(ctx) => ctx as *mut ffi::c_void, + // #[cfg(gles_glx_backend)] + // glutin::context::RawContext::Glx(ctx) => ctx as *mut ffi::c_void, + #[cfg(gles_wgl_backend)] + glutin::context::RawContext::Wgl(ctx) => ctx as *mut ffi::c_void, + #[cfg(gles_cgl_backend)] + glutin::context::RawContext::Cgl(ctx) => ctx as *mut ffi::c_void, + } + } +} + +pub struct AdapterContext { + inner: Arc>, +} + +unsafe impl Sync for AdapterContext {} +unsafe impl Send for AdapterContext {} + +impl AdapterContext { + pub fn is_owned(&self) -> bool { + true + } + + pub fn raw_context(&self) -> *mut ffi::c_void { + self.inner.lock().context.raw_context() + } + + pub fn lock(&self) -> AdapterContextLock<'_> { + let mut inner = self + .inner + // Don't lock forever. If it takes longer than 1 second to get the lock we've got a + // deadlock and should panic to show where we got stuck + .try_lock_for(Duration::from_secs(CONTEXT_LOCK_TIMEOUT_SECS)) + .expect("Could not lock adapter context. This is most-likely a deadlock."); + + inner.lock_surface().unwrap(); + + AdapterContextLock { inner } + } + + fn lock_with_surface( + &self, + surface: &GlutinWindowSurface, + ) -> Result, glutin::error::Error> { + let mut inner = self + .inner + // Don't lock forever. If it takes longer than 1 second to get the lock we've got a + // deadlock and should panic to show where we got stuck + .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(surface)?; + + Ok(AdapterContextLock { inner }) + } +} + +pub struct AdapterContextLock<'a> { + inner: MutexGuard<'a, Inner>, +} + +impl<'a> core::ops::Deref for AdapterContextLock<'a> { + type Target = glow::Context; + + fn deref(&self) -> &Self::Target { + &self.inner.gl + } +} + +impl<'a> Drop for AdapterContextLock<'a> { + fn drop(&mut self) { + self.inner.context.unmake_current().unwrap(); + } +} + +struct Inner { + gl: ManuallyDrop, + context: GlutinContext, + surface: Arc>>, +} + +unsafe impl Send for Inner {} +unsafe impl Sync for Inner {} + +impl Inner { + fn lock_surface(&mut self) -> Result<(), glutin::error::Error> { + if let Some(surface) = self.surface.lock().as_ref() { + self.context.make_current(surface)?; + } + Ok(()) + } +} + +impl Drop for Inner { + fn drop(&mut self) { + struct Guard<'a> { + context: &'a mut GlutinContext, + } + + impl<'a> Drop for Guard<'a> { + fn drop(&mut self) { + self.context.unmake_current().unwrap(); + } + } + + let surface = self.surface.lock(); + let _guard = surface.as_ref().map(|surface| { + self.context.make_current(surface).unwrap(); + Guard { + context: &mut self.context, + } + }); + unsafe { ManuallyDrop::drop(&mut self.gl) }; + } +} + +#[cfg(windows)] +fn preference_default( + window_handle: raw_window_handle::RawWindowHandle, +) -> glutin::display::DisplayApiPreference { + #[cfg(all(gles_wgl_backend, gles_egl_backend))] + let preference = glutin::display::DisplayApiPreference::WglThenEgl(Some(window_handle)); + #[cfg(all(gles_wgl_backend, not(gles_egl_backend)))] + let preference = glutin::display::DisplayApiPreference::Wgl(Some(window_handle.as_raw())); + #[cfg(all(not(gles_wgl_backend), gles_egl_backend))] + let preference = glutin::display::DisplayApiPreference::Egl; + + preference +} + +#[cfg(free_unix)] +fn preference_default( + _window_handle: raw_window_handle::RawWindowHandle, +) -> glutin::display::DisplayApiPreference { + // TODO: Add Surport for x11 gl + // #[cfg(all(gles_egl_backend, gles_glx_backend))] + // let preference = glutin::display::DisplayApiPreference::GlxThenEgl() + // #[cfg(all(gles_glx_backend, not(gles_egl_backend)))] + // let preference = glutin::display::DisplayApiPreference::Glx + #[cfg(all(gles_egl_backend, not(gles_glx_backend)))] + let preference = glutin::display::DisplayApiPreference::Egl; + + preference +} + +#[cfg(apple)] +fn preference_default( + _window_handle: raw_window_handle::RawWindowHandle, +) -> glutin::display::DisplayApiPreference { + #[cfg(all(gles_cgl_backend))] + let preference = glutin::display::DisplayApiPreference::Cgl; + + preference +} + +pub struct Instance { + inner: Arc>, + display: GlutinDisplay, + window: raw_window_handle::RawWindowHandle, + config: glutin::config::Config, + options: wgt::GlBackendOptions, + srgb_capable: bool, +} + +unsafe impl Send for Instance {} +unsafe impl Sync for Instance {} + +impl crate::Instance for Instance { + type A = super::Api; + + unsafe fn init(desc: &crate::InstanceDescriptor<'_>) -> Result { + profiling::scope!("Init OpenGL Backend"); + + let window_handle = desc.window.ok_or(crate::InstanceError::new(String::from( + "RawWindowHandle is required to create OpenGL Instance", + )))?; + let display_handle = desc.display.ok_or(crate::InstanceError::new(String::from( + "RawDisplayHandle is required to create OpenGL Instance", + )))?; + + let preference = preference_default(window_handle.as_raw()); + let display = unsafe { + GlutinDisplay::new(display_handle.as_raw(), preference).map_err(|e| { + crate::InstanceError::with_source( + format!("Failed to create glutin display: {}", e), + e, + ) + })? + }; + + let config_template = glutin::config::ConfigTemplateBuilder::new().build(); + let configs = unsafe { + display + .find_configs(config_template) + .map_err(|e| { + crate::InstanceError::with_source( + String::from("Failed to find suitable glutin config"), + e, + ) + })? + .collect::>() + }; + + if log::log_enabled!(log::Level::Trace) { + for (i, config) in configs.iter().enumerate() { + log::trace!("GL Config {}: {:#?}", i, config); + } + } + + // TODO: Surface attributes can be customized later + let config = configs + .first() + .ok_or_else(|| { + crate::InstanceError::new(String::from( + "No suitable glutin config found for the display", + )) + })? + .clone(); + + let context_attributes = glutin::context::ContextAttributesBuilder::new() + .with_debug(desc.flags.contains(wgt::InstanceFlags::DEBUG)) + .build(desc.window.map(|w| w.as_raw())); + + let context = unsafe { + display + .create_context(&config, &context_attributes) + .map_err(|e| { + crate::InstanceError::with_source( + String::from("Failed to create glutin context"), + e, + ) + })? + }; + + let mut context = GlutinContext { + current_context: None, + not_current_context: Some(context), + }; + + let surface_attributes = GlutinWindowSurfaceAttributesBuilder::new().build( + window_handle.as_raw(), + NonZeroU32::new(1).unwrap(), + NonZeroU32::new(1).unwrap(), + ); + + let surface = unsafe { + display + .create_window_surface(&config, &surface_attributes) + .map_err(|e| { + crate::InstanceError::with_source( + String::from("Failed to create glutin pbuffer surface"), + e, + ) + })? + }; + + context.make_current(&surface).map_err(|e| { + crate::InstanceError::with_source( + String::from("Failed to make glutin context current with pbuffer"), + e, + ) + })?; + let mut gl = unsafe { + glow::Context::from_loader_function(|name| { + display.get_proc_address(ffi::CStr::from_bytes_with_nul_unchecked(name.as_bytes())) + as _ + }) + }; + + // check for sRGB capability + let srgb_capable = config.srgb_capable(); + + // In contrast to OpenGL ES, OpenGL requires explicitly enabling sRGB conversions, + // as otherwise the user has to do the sRGB conversion. + if srgb_capable { + unsafe { gl.enable(glow::FRAMEBUFFER_SRGB) }; + } + + if desc.flags.contains(wgt::InstanceFlags::VALIDATION) && gl.supports_debug() { + log::debug!("Enabling GL debug output"); + unsafe { gl.enable(glow::DEBUG_OUTPUT) }; + unsafe { gl.debug_message_callback(super::gl_debug_message_callback) }; + } + + let gl = ManuallyDrop::new(gl); + context.unmake_current().map_err(|e| { + crate::InstanceError::with_source( + String::from("Failed to unmake glutin context current after initialization"), + e, + ) + })?; + + let inner = Inner { + gl, + context, + surface: Arc::new(Mutex::new(Some(surface))), + }; + Ok(Self { + inner: Arc::new(Mutex::new(inner)), + display, + window: window_handle.as_raw(), + config, + options: desc.backend_options.gl.clone(), + srgb_capable, + }) + } + + unsafe fn create_surface( + &self, + _display_handle: raw_window_handle::RawDisplayHandle, + window_handle: raw_window_handle::RawWindowHandle, + ) -> Result { + Ok(Surface { + display: self.display.clone(), + window: window_handle, + parent: self.window, + config: self.config.clone(), + presentable: true, + swapchain: RwLock::new(None), + srgb_capable: self.srgb_capable, + }) + } + + unsafe fn enumerate_adapters( + &self, + _surface_hint: Option<&Surface>, + ) -> Vec> { + unsafe { + super::Adapter::expose( + AdapterContext { + inner: self.inner.clone(), + }, + self.options.clone(), + ) + } + .into_iter() + .collect() + } +} + +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 ffi::c_void, + options: wgt::GlBackendOptions, + ) -> Option> { + let context = unsafe { glow::Context::from_loader_function(fun) }; + unsafe { + Self::expose( + AdapterContext { + inner: Arc::new(Mutex::new(Inner { + gl: ManuallyDrop::new(context), + context: GlutinContext { + current_context: None, + not_current_context: None, + }, + surface: Arc::new(Mutex::new(None)), + })), + }, + options, + ) + } + } + + pub fn adapter_context(&self) -> &AdapterContext { + &self.shared.context + } +} + +impl super::Device { + pub fn context(&self) -> &AdapterContext { + &self.shared.context + } +} + +#[derive(Debug)] +pub struct SwapchainInner { + framebuffer: glow::Framebuffer, + renderbuffer: glow::Renderbuffer, + /// Extent because the window lies + extent: wgt::Extent3d, + format: wgt::TextureFormat, + format_desc: super::TextureFormatDesc, + #[allow(unused)] + sample_type: wgt::TextureSampleType, +} + +pub enum Swapchain { + Parent(SwapchainInner), + Other(GlutinWindowSurface, SwapchainInner), +} + +pub struct Surface { + display: glutin::display::Display, + window: raw_window_handle::RawWindowHandle, + parent: raw_window_handle::RawWindowHandle, + config: glutin::config::Config, + pub(crate) presentable: bool, + swapchain: RwLock>, + srgb_capable: bool, +} + +unsafe impl Send for Surface {} +unsafe impl Sync for Surface {} + +impl Surface { + pub(super) unsafe fn present( + &self, + _suf_texture: super::Texture, + context: &AdapterContext, + ) -> Result<(), crate::SurfaceError> { + let swapchain = self.swapchain.read(); + let sc = swapchain.as_ref().ok_or(crate::SurfaceError::Other( + "Surface has no swap-chain configured", + ))?; + + let (gl, sci) = match sc { + Swapchain::Other(sc_surface, sc) => { + let gl = context + .lock_with_surface(sc_surface) + .map_err(|_| crate::SurfaceError::Other("Failed to lock adapter context"))?; + (gl, sc) + } + Swapchain::Parent(sc) => { + let gl = context.lock(); + (gl, sc) + } + }; + + // Need ? + // unsafe { gl.disable(glow::SCISSOR_TEST) }; + // unsafe { gl.color_mask(true, true, true, true) }; + + unsafe { gl.bind_framebuffer(glow::DRAW_FRAMEBUFFER, None) }; + unsafe { gl.bind_framebuffer(glow::READ_FRAMEBUFFER, Some(sci.framebuffer)) }; + + if self.srgb_capable { + // Disable sRGB conversions for `glBlitFramebuffer` as behavior does diverge between + // drivers and formats otherwise and we want to ensure no sRGB conversions happen. + unsafe { gl.disable(glow::FRAMEBUFFER_SRGB) }; + } + + // Note the Y-flipping here. GL's presentation is not flipped, + // but main rendering is. Therefore, we Y-flip the output positions + // in the shader, and also this blit. + unsafe { + gl.blit_framebuffer( + 0, + sci.extent.height as i32, + sci.extent.width as i32, + 0, + 0, + 0, + sci.extent.width as i32, + sci.extent.height as i32, + glow::COLOR_BUFFER_BIT, + glow::NEAREST, + ) + }; + + if self.srgb_capable { + unsafe { gl.enable(glow::FRAMEBUFFER_SRGB) }; + } + + unsafe { gl.bind_renderbuffer(glow::RENDERBUFFER, None) }; + unsafe { gl.bind_framebuffer(glow::READ_FRAMEBUFFER, None) }; + + match sc { + Swapchain::Other(sc_surface, _) => { + sc_surface + .swap_buffers(gl.inner.context.current_context.as_ref().unwrap()) + .map_err(|_| { + crate::SurfaceError::Other("Failed to swap buffers on glutin surface") + })?; + } + Swapchain::Parent(_) => { + let surface = gl.inner.surface.lock(); + let parent_surface = surface.as_ref().ok_or(crate::SurfaceError::Other( + "Parent surface is not available for buffer swap", + ))?; + parent_surface + .swap_buffers(gl.inner.context.current_context.as_ref().unwrap()) + .map_err(|_| { + crate::SurfaceError::Other( + "Failed to swap buffers on parent glutin surface", + ) + })?; + } + } + + Ok(()) + } + + pub fn supports_srgb(&self) -> bool { + self.srgb_capable + } + + fn create_swapchain( + &self, + device: &super::Device, + config: &crate::SurfaceConfiguration, + gl: &AdapterContextLock<'_>, + ) -> Result { + let format_desc = device.shared.describe_texture_format(config.format); + + let renderbuffer = unsafe { gl.create_renderbuffer() }.map_err(|error| { + log::error!("Internal swapchain renderbuffer creation failed: {error}"); + crate::DeviceError::OutOfMemory + })?; + unsafe { gl.bind_renderbuffer(glow::RENDERBUFFER, Some(renderbuffer)) }; + unsafe { + gl.renderbuffer_storage( + glow::RENDERBUFFER, + format_desc.internal, + config.extent.width as _, + config.extent.height as _, + ) + }; + + let framebuffer = unsafe { gl.create_framebuffer() }.map_err(|error| { + log::error!("Internal swapchain framebuffer creation failed: {error}"); + crate::DeviceError::OutOfMemory + })?; + unsafe { gl.bind_framebuffer(glow::FRAMEBUFFER, Some(framebuffer)) }; + unsafe { + gl.framebuffer_renderbuffer( + glow::FRAMEBUFFER, + glow::COLOR_ATTACHMENT0, + glow::RENDERBUFFER, + Some(renderbuffer), + ) + }; + + unsafe { gl.bind_renderbuffer(glow::RENDERBUFFER, None) }; + unsafe { gl.bind_framebuffer(glow::READ_FRAMEBUFFER, None) }; + + Ok(SwapchainInner { + renderbuffer, + framebuffer, + extent: config.extent, + format: config.format, + format_desc, + sample_type: wgt::TextureSampleType::Float { filterable: false }, + }) + } +} + +impl crate::Surface for Surface { + type A = super::Api; + + unsafe fn configure( + &self, + device: &super::Device, + config: &crate::SurfaceConfiguration, + ) -> Result<(), crate::SurfaceError> { + let swapchain = match self.swapchain.write().take() { + Some(Swapchain::Other(sc_surface, sc)) => { + let gl = &device + .shared + .context + .lock_with_surface(&sc_surface) + .map_err(|_| { + crate::SurfaceError::Other( + "Failed to lock adapter context for swapchain re-configuration", + ) + })?; + unsafe { gl.delete_framebuffer(sc.framebuffer) }; + unsafe { gl.delete_renderbuffer(sc.renderbuffer) }; + + sc_surface.resize( + gl.inner.context.current_context.as_ref().unwrap(), + unsafe { NonZeroU32::new_unchecked(config.extent.width) }, + unsafe { NonZeroU32::new_unchecked(config.extent.height) }, + ); + + self.create_swapchain(device, config, &gl) + .map(|sc_inner| Swapchain::Other(sc_surface, sc_inner))? + } + Some(Swapchain::Parent(sc)) => { + let gl = &device.shared.context.lock(); + unsafe { gl.delete_framebuffer(sc.framebuffer) }; + unsafe { gl.delete_renderbuffer(sc.renderbuffer) }; + + let surface = gl.inner.surface.lock(); + let parent_surface = surface.as_ref().ok_or(crate::SurfaceError::Other( + "Parent surface is not available for swapchain re-configuration", + ))?; + parent_surface.resize( + gl.inner.context.current_context.as_ref().unwrap(), + unsafe { NonZeroU32::new_unchecked(config.extent.width) }, + unsafe { NonZeroU32::new_unchecked(config.extent.height) }, + ); + + self.create_swapchain(device, config, &gl) + .map(Swapchain::Parent)? + } + None if self.window == self.parent => { + let gl = &device.shared.context.lock(); + let surface = gl.inner.surface.lock(); + if let Some(surface) = surface.as_ref() { + surface.resize( + gl.inner.context.current_context.as_ref().unwrap(), + unsafe { NonZeroU32::new_unchecked(config.extent.width) }, + unsafe { NonZeroU32::new_unchecked(config.extent.height) }, + ); + } else { + return Err(crate::SurfaceError::Other( + "Parent surface is not available for swapchain configuration", + )); + } + self.create_swapchain(device, config, &gl) + .map(Swapchain::Parent)? + } + None => { + let surface_attributes = GlutinWindowSurfaceAttributesBuilder::new() + .with_single_buffer(false) + // .with_srgb(config.format.describe().srgb) + .build( + self.window, + NonZeroU32::new(config.extent.width).unwrap(), + NonZeroU32::new(config.extent.height).unwrap(), + ); + + let surface = unsafe { + self.display + .create_window_surface(&self.config, &surface_attributes) + // TODO: map error properly + .map_err(|_| { + crate::SurfaceError::Other("Failed to create glutin surface") + })? + }; + + let gl = &device + .shared + .context + .lock_with_surface(&surface) + .map_err(|_| { + crate::SurfaceError::Other( + "Failed to lock adapter context for swapchain configuration", + ) + })?; + + self.create_swapchain(device, config, gl) + .map(|sc_inner| Swapchain::Other(surface, sc_inner))? + } + }; + + let mut swapchain_write = self.swapchain.write(); + *swapchain_write = Some(swapchain); + Ok(()) + } + + unsafe fn unconfigure(&self, device: &super::Device) { + match self.swapchain.write().take() { + Some(Swapchain::Other(sc_surface, sc)) => { + let gl = &device + .shared + .context + .lock_with_surface(&sc_surface) + .unwrap(); + unsafe { gl.delete_framebuffer(sc.framebuffer) }; + unsafe { gl.delete_renderbuffer(sc.renderbuffer) }; + } + Some(Swapchain::Parent(sc)) => { + let gl = &device.shared.context.lock(); + unsafe { gl.delete_framebuffer(sc.framebuffer) }; + unsafe { gl.delete_renderbuffer(sc.renderbuffer) }; + } + None => {} + } + } + + unsafe fn acquire_texture( + &self, + _timeout_ms: Option, + _fence: &super::Fence, + ) -> Result>, crate::SurfaceError> { + let swapchain = self.swapchain.read(); + let sc = match swapchain.as_ref().ok_or(crate::SurfaceError::Other( + "Surface has no swap-chain configured", + ))? { + Swapchain::Other(_, sc) => sc, + Swapchain::Parent(sc) => sc, + }; + + let texture = super::Texture { + inner: super::TextureInner::Renderbuffer { + raw: sc.renderbuffer, + }, + drop_guard: None, + array_layer_count: 1, + mip_level_count: 1, + format: sc.format, + format_desc: sc.format_desc.clone(), + copy_size: crate::CopyExtent { + width: sc.extent.width, + height: sc.extent.height, + depth: 1, + }, + }; + Ok(Some(crate::AcquiredSurfaceTexture { + texture, + suboptimal: false, + })) + } + + unsafe fn discard_texture(&self, _texture: super::Texture) {} +} diff --git a/wgpu-hal/src/gles/mod.rs b/wgpu-hal/src/gles/mod.rs index c374a1a057..748e8c6038 100644 --- a/wgpu-hal/src/gles/mod.rs +++ b/wgpu-hal/src/gles/mod.rs @@ -80,14 +80,12 @@ we don't bother with that combination. */ ///cbindgen:ignore -#[cfg(not(any(windows, webgl)))] -mod egl; #[cfg(Emscripten)] mod emscripten; +#[cfg(not(webgl))] +mod glutin; #[cfg(webgl)] mod web; -#[cfg(windows)] -mod wgl; mod adapter; mod command; @@ -98,21 +96,16 @@ mod queue; pub use fence::Fence; -#[cfg(not(any(windows, webgl)))] -pub use self::egl::{AdapterContext, AdapterContextLock}; -#[cfg(not(any(windows, webgl)))] -pub use self::egl::{Instance, Surface}; +#[cfg(not(webgl))] +use self::glutin::AdapterContext; +#[cfg(not(webgl))] +pub use self::glutin::{Instance, Surface}; #[cfg(webgl)] pub use self::web::AdapterContext; #[cfg(webgl)] pub use self::web::{Instance, Surface}; -#[cfg(windows)] -use self::wgl::AdapterContext; -#[cfg(windows)] -pub use self::wgl::{Instance, Surface}; - use alloc::{boxed::Box, string::String, string::ToString as _, sync::Arc, vec::Vec}; use core::{ fmt, diff --git a/wgpu-hal/src/gles/wgl.rs b/wgpu-hal/src/gles/wgl.rs deleted file mode 100644 index f9a1af09be..0000000000 --- a/wgpu-hal/src/gles/wgl.rs +++ /dev/null @@ -1,900 +0,0 @@ -use alloc::{borrow::ToOwned as _, ffi::CString, string::String, sync::Arc, vec::Vec}; -use core::{ - ffi::{c_int, c_void, CStr}, - mem::{self, ManuallyDrop}, - ptr, - time::Duration, -}; -use std::{ - sync::{ - mpsc::{sync_channel, SyncSender}, - LazyLock, - }, - thread, -}; - -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 hashbrown::HashSet; -use parking_lot::{Mutex, MutexGuard, RwLock}; -use raw_window_handle::{RawDisplayHandle, RawWindowHandle}; -use wgt::InstanceFlags; -use windows::{ - core::{Error, PCSTR}, - Win32::{ - Foundation, - Graphics::{Gdi, OpenGL}, - System::LibraryLoader, - UI::WindowsAndMessaging, - }, -}; - -/// The amount of time to wait while trying to obtain a lock to the adapter context -const CONTEXT_LOCK_TIMEOUT_SECS: u64 = 1; - -/// A wrapper around a `[`glow::Context`]` and the required WGL context that uses locking to -/// guarantee exclusive access when shared with multiple threads. -pub struct AdapterContext { - inner: Arc>, -} - -unsafe impl Sync for AdapterContext {} -unsafe impl Send for AdapterContext {} - -impl AdapterContext { - pub fn is_owned(&self) -> bool { - true - } - - pub fn raw_context(&self) -> *mut c_void { - 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 - /// do rendering. - #[track_caller] - pub fn lock(&self) -> AdapterContextLock<'_> { - let inner = self - .inner - // Don't lock forever. If it takes longer than 1 second to get the lock we've got a - // deadlock and should panic to show where we got stuck - .try_lock_for(Duration::from_secs(CONTEXT_LOCK_TIMEOUT_SECS)) - .expect("Could not lock adapter context. This is most-likely a deadlock."); - - if let Some(wgl) = &inner.context { - wgl.make_current(inner.device.dc).unwrap() - }; - - AdapterContextLock { inner } - } - - /// Obtain a lock to the WGL context and get handle to the [`glow::Context`] that can be used to - /// do rendering. - /// - /// Unlike [`lock`](Self::lock), this accepts a device to pass to `make_current` and exposes the error - /// when `make_current` fails. - #[track_caller] - fn lock_with_dc(&self, device: Gdi::HDC) -> windows::core::Result> { - let inner = self - .inner - .try_lock_for(Duration::from_secs(CONTEXT_LOCK_TIMEOUT_SECS)) - .expect("Could not lock adapter context. This is most-likely a deadlock."); - - if let Some(wgl) = &inner.context { - wgl.make_current(device)?; - } - - Ok(AdapterContextLock { inner }) - } -} - -/// A guard containing a lock to an [`AdapterContext`], while the GL context is kept current. -pub struct AdapterContextLock<'a> { - inner: MutexGuard<'a, Inner>, -} - -impl<'a> core::ops::Deref for AdapterContextLock<'a> { - type Target = glow::Context; - - fn deref(&self) -> &Self::Target { - &self.inner.gl - } -} - -impl<'a> Drop for AdapterContextLock<'a> { - fn drop(&mut self) { - if let Some(wgl) = &self.inner.context { - wgl.unmake_current().unwrap() - } - } -} - -struct WglContext { - context: OpenGL::HGLRC, -} - -impl WglContext { - fn make_current(&self, device: Gdi::HDC) -> windows::core::Result<()> { - unsafe { OpenGL::wglMakeCurrent(device, self.context) } - } - - fn unmake_current(&self) -> windows::core::Result<()> { - if unsafe { OpenGL::wglGetCurrentContext() }.is_invalid() { - return Ok(()); - } - unsafe { OpenGL::wglMakeCurrent(Default::default(), Default::default()) } - } -} - -impl Drop for WglContext { - fn drop(&mut self) { - if let Err(e) = unsafe { OpenGL::wglDeleteContext(self.context) } { - log::error!("failed to delete WGL context: {e}"); - } - } -} - -unsafe impl Send for WglContext {} -unsafe impl Sync for WglContext {} - -struct Inner { - gl: ManuallyDrop, - device: InstanceDevice, - context: Option, -} - -impl Drop for Inner { - fn drop(&mut self) { - struct CurrentGuard<'a>(&'a WglContext); - impl Drop for CurrentGuard<'_> { - fn drop(&mut self) { - self.0.unmake_current().unwrap(); - } - } - - // Context must be current when dropped. See safety docs on - // `glow::HasContext`. - // - // 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) }; - } -} - -unsafe impl Send for Inner {} -unsafe impl Sync for Inner {} - -pub struct Instance { - srgb_capable: bool, - options: wgt::GlBackendOptions, - inner: Arc>, -} - -unsafe impl Send for Instance {} -unsafe impl Sync for Instance {} - -fn load_gl_func(name: &str, module: Option) -> *const c_void { - let addr = CString::new(name.as_bytes()).unwrap(); - let mut ptr = unsafe { OpenGL::wglGetProcAddress(PCSTR(addr.as_ptr().cast())) }; - if ptr.is_none() { - if let Some(module) = module { - ptr = unsafe { LibraryLoader::GetProcAddress(module, PCSTR(addr.as_ptr().cast())) }; - } - } - ptr.map_or_else(ptr::null_mut, |p| p as *mut c_void) -} - -fn get_extensions(extra: &Wgl, dc: Gdi::HDC) -> HashSet { - if extra.GetExtensionsStringARB.is_loaded() { - unsafe { CStr::from_ptr(extra.GetExtensionsStringARB(dc.0)) } - .to_str() - .unwrap_or("") - } else { - "" - } - .split(' ') - .map(|s| s.to_owned()) - .collect() -} - -unsafe fn setup_pixel_format(dc: Gdi::HDC) -> Result<(), crate::InstanceError> { - { - let format = OpenGL::PIXELFORMATDESCRIPTOR { - nVersion: 1, - nSize: size_of::() as u16, - dwFlags: OpenGL::PFD_DRAW_TO_WINDOW - | OpenGL::PFD_SUPPORT_OPENGL - | OpenGL::PFD_DOUBLEBUFFER, - iPixelType: OpenGL::PFD_TYPE_RGBA, - cColorBits: 8, - ..unsafe { mem::zeroed() } - }; - - let index = unsafe { OpenGL::ChoosePixelFormat(dc, &format) }; - if index == 0 { - return Err(crate::InstanceError::with_source( - String::from("unable to choose pixel format"), - Error::from_thread(), - )); - } - - let current = unsafe { OpenGL::GetPixelFormat(dc) }; - - if index != current { - unsafe { OpenGL::SetPixelFormat(dc, index, &format) }.map_err(|e| { - crate::InstanceError::with_source(String::from("unable to set pixel format"), e) - })?; - } - } - - { - let index = unsafe { OpenGL::GetPixelFormat(dc) }; - if index == 0 { - return Err(crate::InstanceError::with_source( - String::from("unable to get pixel format index"), - Error::from_thread(), - )); - } - let mut format = Default::default(); - if unsafe { - OpenGL::DescribePixelFormat(dc, index, size_of_val(&format) as u32, Some(&mut format)) - } == 0 - { - return Err(crate::InstanceError::with_source( - String::from("unable to read pixel format"), - Error::from_thread(), - )); - } - - if !format.dwFlags.contains(OpenGL::PFD_SUPPORT_OPENGL) - || format.iPixelType != OpenGL::PFD_TYPE_RGBA - { - return Err(crate::InstanceError::new(String::from( - "unsuitable pixel format", - ))); - } - } - Ok(()) -} - -fn create_global_window_class() -> Result { - let instance = unsafe { LibraryLoader::GetModuleHandleA(None) }.map_err(|e| { - crate::InstanceError::with_source(String::from("unable to get executable instance"), e) - })?; - - // Use the address of `UNIQUE` as part of the window class name to ensure different - // `wgpu` versions use different names. - static UNIQUE: Mutex = Mutex::new(0); - let class_addr: *const _ = &UNIQUE; - let name = format!("wgpu Device Class {:x}\0", class_addr as usize); - let name = CString::from_vec_with_nul(name.into_bytes()).unwrap(); - - // The window class may already be registered if we are a dynamic library that got - // unloaded & loaded back into the same process. If so, just skip creation. - let already_exists = unsafe { - let mut wc = mem::zeroed::(); - WindowsAndMessaging::GetClassInfoExA( - Some(instance.into()), - PCSTR(name.as_ptr().cast()), - &mut wc, - ) - .is_ok() - }; - if already_exists { - return Ok(name); - } - - // Use a wrapper function for compatibility with `windows-rs`. - unsafe extern "system" fn wnd_proc( - window: Foundation::HWND, - msg: u32, - wparam: Foundation::WPARAM, - lparam: Foundation::LPARAM, - ) -> Foundation::LRESULT { - unsafe { WindowsAndMessaging::DefWindowProcA(window, msg, wparam, lparam) } - } - - let window_class = WindowsAndMessaging::WNDCLASSEXA { - cbSize: size_of::() as u32, - style: WindowsAndMessaging::CS_OWNDC, - lpfnWndProc: Some(wnd_proc), - cbClsExtra: 0, - cbWndExtra: 0, - hInstance: instance.into(), - hIcon: WindowsAndMessaging::HICON::default(), - hCursor: WindowsAndMessaging::HCURSOR::default(), - hbrBackground: Gdi::HBRUSH::default(), - lpszMenuName: PCSTR::null(), - lpszClassName: PCSTR(name.as_ptr().cast()), - hIconSm: WindowsAndMessaging::HICON::default(), - }; - - let atom = unsafe { WindowsAndMessaging::RegisterClassExA(&window_class) }; - - if atom == 0 { - return Err(crate::InstanceError::with_source( - String::from("unable to register window class"), - Error::from_thread(), - )); - } - - // We intentionally leak the window class as we only need one per process. - - Ok(name) -} - -fn get_global_window_class() -> Result { - static GLOBAL: LazyLock> = - LazyLock::new(create_global_window_class); - GLOBAL.clone() -} - -struct InstanceDevice { - dc: Gdi::HDC, - - /// This is used to keep the thread owning `dc` alive until this struct is dropped. - _tx: SyncSender<()>, -} - -fn create_instance_device() -> Result { - #[derive(Clone, Copy)] - // TODO: We can get these SendSync definitions in the upstream metadata if this is the case - struct SendDc(Gdi::HDC); - unsafe impl Sync for SendDc {} - unsafe impl Send for SendDc {} - - struct Window { - window: Foundation::HWND, - } - impl Drop for Window { - fn drop(&mut self) { - if let Err(e) = unsafe { WindowsAndMessaging::DestroyWindow(self.window) } { - log::error!("failed to destroy window: {e}"); - } - } - } - - let window_class = get_global_window_class()?; - - let (drop_tx, drop_rx) = sync_channel(0); - let (setup_tx, setup_rx) = sync_channel(0); - - // We spawn a thread which owns the hidden window for this instance. - thread::Builder::new() - .stack_size(256 * 1024) - .name("wgpu-hal WGL Instance Thread".to_owned()) - .spawn(move || { - let setup = (|| { - let instance = unsafe { LibraryLoader::GetModuleHandleA(None) }.map_err(|e| { - crate::InstanceError::with_source( - String::from("unable to get executable instance"), - e, - ) - })?; - - // Create a hidden window since we don't pass `WS_VISIBLE`. - let window = unsafe { - WindowsAndMessaging::CreateWindowExA( - WindowsAndMessaging::WINDOW_EX_STYLE::default(), - PCSTR(window_class.as_ptr().cast()), - PCSTR(window_class.as_ptr().cast()), - WindowsAndMessaging::WINDOW_STYLE::default(), - 0, - 0, - 1, - 1, - None, - None, - Some(instance.into()), - None, - ) - } - .map_err(|e| { - crate::InstanceError::with_source( - String::from("unable to create hidden instance window"), - e, - ) - })?; - let window = Window { window }; - - let dc = unsafe { Gdi::GetDC(Some(window.window)) }; - if dc.is_invalid() { - return Err(crate::InstanceError::with_source( - String::from("unable to create memory device"), - Error::from_thread(), - )); - } - let dc = DeviceContextHandle { - device: dc, - window: window.window, - }; - unsafe { setup_pixel_format(dc.device)? }; - - Ok((window, dc)) - })(); - - match setup { - Ok((_window, dc)) => { - setup_tx.send(Ok(SendDc(dc.device))).unwrap(); - // Wait for the shutdown event to free the window and device context handle. - drop_rx.recv().ok(); - } - Err(err) => { - setup_tx.send(Err(err)).unwrap(); - } - } - }) - .map_err(|e| { - crate::InstanceError::with_source(String::from("unable to create instance thread"), e) - })?; - - let dc = setup_rx.recv().unwrap()?.0; - - Ok(InstanceDevice { dc, _tx: drop_tx }) -} - -impl crate::Instance for Instance { - type A = super::Api; - - unsafe fn init(desc: &crate::InstanceDescriptor<'_>) -> Result { - profiling::scope!("Init OpenGL (WGL) Backend"); - let opengl_module = - unsafe { LibraryLoader::LoadLibraryA(PCSTR(c"opengl32.dll".as_ptr().cast())) } - .map_err(|e| { - crate::InstanceError::with_source( - String::from("unable to load the OpenGL library"), - e, - ) - })?; - - let device = create_instance_device()?; - let dc = device.dc; - - let context = unsafe { OpenGL::wglCreateContext(dc) }.map_err(|e| { - crate::InstanceError::with_source( - String::from("unable to create initial OpenGL context"), - e, - ) - })?; - let context = WglContext { context }; - context.make_current(dc).map_err(|e| { - crate::InstanceError::with_source( - String::from("unable to set initial OpenGL context as current"), - e, - ) - })?; - - let extra = Wgl::load_with(|name| load_gl_func(name, None)); - let extensions = get_extensions(&extra, dc); - - let can_use_profile = extensions.contains("WGL_ARB_create_context_profile") - && extra.CreateContextAttribsARB.is_loaded(); - - let context = if can_use_profile { - let attributes = [ - CONTEXT_PROFILE_MASK_ARB as c_int, - CONTEXT_CORE_PROFILE_BIT_ARB as c_int, - CONTEXT_FLAGS_ARB as c_int, - if desc.flags.contains(InstanceFlags::DEBUG) { - CONTEXT_DEBUG_BIT_ARB as c_int - } else { - 0 - }, - 0, // End of list - ]; - let context = - unsafe { extra.CreateContextAttribsARB(dc.0, ptr::null(), attributes.as_ptr()) }; - if context.is_null() { - return Err(crate::InstanceError::with_source( - String::from("unable to create OpenGL context"), - Error::from_thread(), - )); - } - WglContext { - context: OpenGL::HGLRC(context.cast_mut()), - } - } else { - context - }; - - context.make_current(dc).map_err(|e| { - crate::InstanceError::with_source( - String::from("unable to set OpenGL context as current"), - e, - ) - })?; - - let mut gl = unsafe { - glow::Context::from_loader_function(|name| load_gl_func(name, Some(opengl_module))) - }; - - let extra = Wgl::load_with(|name| load_gl_func(name, None)); - let extensions = get_extensions(&extra, dc); - - let srgb_capable = extensions.contains("WGL_EXT_framebuffer_sRGB") - || extensions.contains("WGL_ARB_framebuffer_sRGB") - || gl - .supported_extensions() - .contains("GL_ARB_framebuffer_sRGB"); - - // In contrast to OpenGL ES, OpenGL requires explicitly enabling sRGB conversions, - // as otherwise the user has to do the sRGB conversion. - if srgb_capable { - unsafe { gl.enable(glow::FRAMEBUFFER_SRGB) }; - } - - if desc.flags.contains(InstanceFlags::VALIDATION) && gl.supports_debug() { - log::debug!("Enabling GL debug output"); - unsafe { gl.enable(glow::DEBUG_OUTPUT) }; - unsafe { gl.debug_message_callback(super::gl_debug_message_callback) }; - } - - // 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( - String::from("unable to unset the current WGL context"), - e, - ) - })?; - - Ok(Instance { - inner: Arc::new(Mutex::new(Inner { - device, - gl, - context: Some(context), - })), - options: desc.backend_options.gl.clone(), - srgb_capable, - }) - } - - #[cfg_attr(target_os = "macos", allow(unused, unused_mut, unreachable_code))] - unsafe fn create_surface( - &self, - _display_handle: RawDisplayHandle, - window_handle: RawWindowHandle, - ) -> Result { - let window = if let RawWindowHandle::Win32(handle) = window_handle { - handle - } else { - return Err(crate::InstanceError::new(format!( - "unsupported window: {window_handle:?}" - ))); - }; - Ok(Surface { - // This cast exists because of https://github.com/rust-windowing/raw-window-handle/issues/171 - window: Foundation::HWND(window.hwnd.get() as *mut _), - presentable: true, - swapchain: RwLock::new(None), - srgb_capable: self.srgb_capable, - }) - } - - unsafe fn enumerate_adapters( - &self, - _surface_hint: Option<&Surface>, - ) -> Vec> { - unsafe { - super::Adapter::expose( - AdapterContext { - inner: self.inner.clone(), - }, - self.options.clone(), - ) - } - .into_iter() - .collect() - } -} - -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, - options: wgt::GlBackendOptions, - ) -> 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, - })), - }, - options, - ) - } - } - - 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, -} - -impl Drop for DeviceContextHandle { - fn drop(&mut self) { - unsafe { - Gdi::ReleaseDC(Some(self.window), self.device); - }; - } -} - -pub struct Swapchain { - framebuffer: glow::Framebuffer, - renderbuffer: glow::Renderbuffer, - - /// Extent because the window lies - extent: wgt::Extent3d, - - format: wgt::TextureFormat, - format_desc: super::TextureFormatDesc, - #[allow(unused)] - sample_type: wgt::TextureSampleType, -} - -pub struct Surface { - window: Foundation::HWND, - pub(super) presentable: bool, - swapchain: RwLock>, - srgb_capable: bool, -} - -unsafe impl Send for Surface {} -unsafe impl Sync for Surface {} - -impl Surface { - pub(super) unsafe fn present( - &self, - _suf_texture: super::Texture, - context: &AdapterContext, - ) -> Result<(), crate::SurfaceError> { - let swapchain = self.swapchain.read(); - let sc = swapchain.as_ref().unwrap(); - let dc = unsafe { Gdi::GetDC(Some(self.window)) }; - if dc.is_invalid() { - log::error!( - "unable to get the device context from window: {}", - Error::from_thread() - ); - return Err(crate::SurfaceError::Other( - "unable to get the device context from window", - )); - } - let dc = DeviceContextHandle { - device: dc, - window: self.window, - }; - - let gl = context.lock_with_dc(dc.device).map_err(|e| { - log::error!("unable to make the OpenGL context current for surface: {e}",); - crate::SurfaceError::Other("unable to make the OpenGL context current for surface") - })?; - - unsafe { gl.bind_framebuffer(glow::DRAW_FRAMEBUFFER, None) }; - unsafe { gl.bind_framebuffer(glow::READ_FRAMEBUFFER, Some(sc.framebuffer)) }; - - if self.srgb_capable { - // Disable sRGB conversions for `glBlitFramebuffer` as behavior does diverge between - // drivers and formats otherwise and we want to ensure no sRGB conversions happen. - unsafe { gl.disable(glow::FRAMEBUFFER_SRGB) }; - } - - // Note the Y-flipping here. GL's presentation is not flipped, - // but main rendering is. Therefore, we Y-flip the output positions - // in the shader, and also this blit. - unsafe { - gl.blit_framebuffer( - 0, - sc.extent.height as i32, - sc.extent.width as i32, - 0, - 0, - 0, - sc.extent.width as i32, - sc.extent.height as i32, - glow::COLOR_BUFFER_BIT, - glow::NEAREST, - ) - }; - - if self.srgb_capable { - unsafe { gl.enable(glow::FRAMEBUFFER_SRGB) }; - } - - unsafe { gl.bind_renderbuffer(glow::RENDERBUFFER, None) }; - unsafe { gl.bind_framebuffer(glow::READ_FRAMEBUFFER, None) }; - - if let Err(e) = unsafe { OpenGL::SwapBuffers(dc.device) } { - log::error!("unable to swap buffers: {e}"); - return Err(crate::SurfaceError::Other("unable to swap buffers")); - } - - Ok(()) - } - - pub fn supports_srgb(&self) -> bool { - self.srgb_capable - } -} - -impl crate::Surface for Surface { - type A = super::Api; - - unsafe fn configure( - &self, - device: &super::Device, - config: &crate::SurfaceConfiguration, - ) -> Result<(), crate::SurfaceError> { - // Remove the old configuration. - unsafe { self.unconfigure(device) }; - - let dc = unsafe { Gdi::GetDC(Some(self.window)) }; - if dc.is_invalid() { - log::error!( - "unable to get the device context from window: {}", - Error::from_thread() - ); - return Err(crate::SurfaceError::Other( - "unable to get the device context from window", - )); - } - let dc = DeviceContextHandle { - device: dc, - window: self.window, - }; - - if let Err(e) = unsafe { setup_pixel_format(dc.device) } { - log::error!("unable to setup surface pixel format: {e}",); - return Err(crate::SurfaceError::Other( - "unable to setup surface pixel format", - )); - } - - let format_desc = device.shared.describe_texture_format(config.format); - let gl = &device.shared.context.lock_with_dc(dc.device).map_err(|e| { - log::error!("unable to make the OpenGL context current for surface: {e}",); - crate::SurfaceError::Other("unable to make the OpenGL context current for surface") - })?; - - let renderbuffer = unsafe { gl.create_renderbuffer() }.map_err(|error| { - log::error!("Internal swapchain renderbuffer creation failed: {error}"); - crate::DeviceError::OutOfMemory - })?; - unsafe { gl.bind_renderbuffer(glow::RENDERBUFFER, Some(renderbuffer)) }; - unsafe { - gl.renderbuffer_storage( - glow::RENDERBUFFER, - format_desc.internal, - config.extent.width as _, - config.extent.height as _, - ) - }; - - let framebuffer = unsafe { gl.create_framebuffer() }.map_err(|error| { - log::error!("Internal swapchain framebuffer creation failed: {error}"); - crate::DeviceError::OutOfMemory - })?; - unsafe { gl.bind_framebuffer(glow::READ_FRAMEBUFFER, Some(framebuffer)) }; - unsafe { - gl.framebuffer_renderbuffer( - glow::READ_FRAMEBUFFER, - glow::COLOR_ATTACHMENT0, - glow::RENDERBUFFER, - Some(renderbuffer), - ) - }; - unsafe { gl.bind_renderbuffer(glow::RENDERBUFFER, None) }; - unsafe { gl.bind_framebuffer(glow::READ_FRAMEBUFFER, None) }; - - // Setup presentation mode - let extra = Wgl::load_with(|name| load_gl_func(name, None)); - let extensions = get_extensions(&extra, dc.device); - if !(extensions.contains("WGL_EXT_swap_control") && extra.SwapIntervalEXT.is_loaded()) { - log::error!("WGL_EXT_swap_control is unsupported"); - return Err(crate::SurfaceError::Other( - "WGL_EXT_swap_control is unsupported", - )); - } - - let vsync = match config.present_mode { - wgt::PresentMode::Immediate => false, - wgt::PresentMode::Fifo => true, - _ => { - log::error!("unsupported present mode: {:?}", config.present_mode); - return Err(crate::SurfaceError::Other("unsupported present mode")); - } - }; - - if unsafe { extra.SwapIntervalEXT(if vsync { 1 } else { 0 }) } == Foundation::FALSE.0 { - log::error!("unable to set swap interval: {}", Error::from_thread()); - return Err(crate::SurfaceError::Other("unable to set swap interval")); - } - - self.swapchain.write().replace(Swapchain { - renderbuffer, - framebuffer, - extent: config.extent, - format: config.format, - format_desc, - sample_type: wgt::TextureSampleType::Float { filterable: false }, - }); - - Ok(()) - } - - unsafe fn unconfigure(&self, device: &super::Device) { - let gl = &device.shared.context.lock(); - if let Some(sc) = self.swapchain.write().take() { - unsafe { - gl.delete_renderbuffer(sc.renderbuffer); - gl.delete_framebuffer(sc.framebuffer) - }; - } - } - - unsafe fn acquire_texture( - &self, - _timeout_ms: Option, - _fence: &super::Fence, - ) -> Result>, crate::SurfaceError> { - let swapchain = self.swapchain.read(); - let sc = swapchain.as_ref().unwrap(); - let texture = super::Texture { - inner: super::TextureInner::Renderbuffer { - raw: sc.renderbuffer, - }, - drop_guard: None, - array_layer_count: 1, - mip_level_count: 1, - format: sc.format, - format_desc: sc.format_desc.clone(), - copy_size: crate::CopyExtent { - width: sc.extent.width, - height: sc.extent.height, - depth: 1, - }, - }; - Ok(Some(crate::AcquiredSurfaceTexture { - texture, - suboptimal: false, - })) - } - unsafe fn discard_texture(&self, _texture: super::Texture) {} -} diff --git a/wgpu-hal/src/lib.rs b/wgpu-hal/src/lib.rs index dc514855db..eac79bd5a5 100644 --- a/wgpu-hal/src/lib.rs +++ b/wgpu-hal/src/lib.rs @@ -305,7 +305,7 @@ use core::{ }; use bitflags::bitflags; -use raw_window_handle::DisplayHandle; +use raw_window_handle::{DisplayHandle, WindowHandle}; use thiserror::Error; use wgt::WasmNotSendSync; @@ -1881,6 +1881,9 @@ pub struct InstanceDescriptor<'a> { /// This is a borrow because the surrounding `core::Instance` keeps the the owned display handle /// alive already. pub display: Option>, + /// This is a borrow because the surrounding `core::Instance` keeps the owned native window handle + /// alive already. + pub window: Option>, } #[derive(Clone, Debug)] diff --git a/wgpu-hal/src/noop/mod.rs b/wgpu-hal/src/noop/mod.rs index c8b59850c8..bef113cf19 100644 --- a/wgpu-hal/src/noop/mod.rs +++ b/wgpu-hal/src/noop/mod.rs @@ -100,6 +100,7 @@ impl crate::Instance for Context { memory_budget_thresholds: _, telemetry: _, display: _, + window: _, } = *desc; if enable { Ok(Context) diff --git a/wgpu-types/src/instance.rs b/wgpu-types/src/instance.rs index ded70816ce..db451d2393 100644 --- a/wgpu-types/src/instance.rs +++ b/wgpu-types/src/instance.rs @@ -17,6 +17,18 @@ impl + WgpuHasWindowHandle for T +{ +} + /// Options for creating an instance. /// /// If you want to allow control of instance settings via environment variables, call either @@ -62,6 +74,14 @@ pub struct InstanceDescriptor { // least `trait WindowHandle: HasWindowHandle + HasDisplayHandle` should really be removed as // it's impractical and not implementable everywhere. pub display: Option>, + + /// System platform or compositor connection to connect this `Instance` to. + /// + /// If not [`None`], it is invalid to pass a different [`raw_window_handle::HasWindowHandle`] to `create_surface()`. + /// + /// - On GLES, this is required when intending to present on the windows. + /// - On Vulkan, Metal and Dx12, this is currently unused. + pub window: Option>, } impl InstanceDescriptor { @@ -87,6 +107,7 @@ impl InstanceDescriptor { memory_budget_thresholds: MemoryBudgetThresholds::default(), backend_options, display: None, + window: None, } }