Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion internal/backends/winit/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,12 @@ x11 = [
"softbuffer?/x11",
"softbuffer?/x11-dlopen",
]
renderer-femtovg = ["i-slint-renderer-femtovg/opengl", "dep:glutin", "dep:glutin-winit"]
renderer-femtovg = [
"i-slint-renderer-femtovg/opengl",
"dep:glutin",
"dep:glutin-winit",
"windows/Win32_Graphics_OpenGL",
]
renderer-femtovg-wgpu = ["i-slint-renderer-femtovg/wgpu", "dep:i-slint-renderer-femtovg", "unstable-wgpu-30"]
renderer-skia = ["i-slint-renderer-skia"]
renderer-vello = ["dep:i-slint-renderer-anyrender", "i-slint-renderer-anyrender/vello", "unstable-wgpu-29"]
Expand Down
11 changes: 10 additions & 1 deletion internal/backends/winit/renderer/femtovg.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Copyright © SixtyFPS GmbH <info@slint.dev>
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0

// cSpell: ignore glcontext webglcontextlost webglcontextrestored
// cSpell: ignore glcontext glprobe webglcontextlost webglcontextrestored
use std::rc::Rc;
#[cfg(supports_opengl)]
use std::rc::Weak;
Expand All @@ -21,6 +21,8 @@ use super::WinitCompatibleRenderer;

#[cfg(all(supports_opengl, not(target_arch = "wasm32")))]
mod glcontext;
#[cfg(all(supports_opengl, target_os = "windows"))]
mod glprobe;

#[cfg(supports_opengl)]
pub struct GlutinFemtoVGRenderer {
Expand All @@ -34,6 +36,13 @@ impl GlutinFemtoVGRenderer {
pub fn new_suspended(
shared_backend_data: &Rc<crate::SharedBackendData>,
) -> Result<Box<dyn WinitCompatibleRenderer>, PlatformError> {
// Bail out before a window is created, so that the backend can still fall back to
// another renderer.
#[cfg(target_os = "windows")]
if !glprobe::opengl_2_available() {
return Err("The FemtoVG renderer requires an OpenGL 2.0 driver".into());
}

Ok(Box::new(Self {
renderer: FemtoVGRenderer::new_suspended(),
_requested_graphics_api: shared_backend_data.requested_graphics_api.clone(),
Expand Down
13 changes: 0 additions & 13 deletions internal/backends/winit/renderer/femtovg/glcontext.rs
Original file line number Diff line number Diff line change
Expand Up @@ -205,19 +205,6 @@ impl OpenGLContext {
ns_view.setLayerContentsPlacement(objc2_app_kit::NSViewLayerContentsPlacement::TopLeft);
}

// Sanity check, as all this might succeed on Windows without working GL drivers, but this will fail:
if context
.display()
.get_proc_address(&std::ffi::CString::new("glCreateShader").unwrap())
.is_null()
{
return Err(
"Failed to initialize OpenGL driver: Could not locate glCreateShader symbol"
.to_string()
.into(),
);
}

// Try to default to vsync and ignore if the driver doesn't support it.
surface
.set_swap_interval(
Expand Down
142 changes: 142 additions & 0 deletions internal/backends/winit/renderer/femtovg/glprobe.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
// Copyright © SixtyFPS GmbH <info@slint.dev>
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0

// cSpell: ignore clipchildren clipsiblings doublebuffer owndc
// cSpell: ignore pixelformatdescriptor wndclassw

use std::sync::OnceLock;

use windows::Win32::Foundation::{HWND, LPARAM, LRESULT, WPARAM};
use windows::Win32::Graphics::Gdi::{GetDC, HDC, ReleaseDC};
use windows::Win32::Graphics::OpenGL::{
ChoosePixelFormat, PFD_DOUBLEBUFFER, PFD_DRAW_TO_WINDOW, PFD_MAIN_PLANE, PFD_SUPPORT_OPENGL,
PFD_TYPE_RGBA, PIXELFORMATDESCRIPTOR, SetPixelFormat, wglCreateContext, wglDeleteContext,
wglGetCurrentContext, wglGetCurrentDC, wglGetProcAddress, wglMakeCurrent,
};
use windows::Win32::UI::WindowsAndMessaging::{
CS_OWNDC, CreateWindowExW, DefWindowProcW, DestroyWindow, RegisterClassW, UnregisterClassW,
WINDOW_EX_STYLE, WNDCLASSW, WS_CLIPCHILDREN, WS_CLIPSIBLINGS, WS_OVERLAPPED,
};
use windows::core::{s, w};

pub fn opengl_2_available() -> bool {
static AVAILABLE: OnceLock<bool> = OnceLock::new();
// A probe that couldn't run says nothing about the driver, and glutin may still
// reach a working one through EGL.
*AVAILABLE.get_or_init(|| unsafe { probe() }.unwrap_or(true))
}

/// Every Windows installation has an opengl32.dll, but in virtual machines it often provides
/// only OpenGL 1.1, without the shader entry points. Creating a context still succeeds there,
/// so probe with a throwaway window and context up-front.
///
/// Returns `None` if the probe couldn't run, which says nothing about the driver.
unsafe fn probe() -> Option<bool> {
unsafe {
let class_name = w!("SlintOpenGLProbe");
let window_class = WNDCLASSW {
style: CS_OWNDC,
lpfnWndProc: Some(window_proc),
lpszClassName: class_name,
..Default::default()
};
if RegisterClassW(&window_class) == 0 {
return None;
}

// WGL requires a window that clips its children and siblings.
let available = CreateWindowExW(
WINDOW_EX_STYLE::default(),
class_name,
w!(""),
WS_OVERLAPPED | WS_CLIPCHILDREN | WS_CLIPSIBLINGS,
0,
0,
1,
1,
None,
None,
None,
None,
)
.ok()
.and_then(|window| {
let available = probe_window(window);
let _ = DestroyWindow(window);
available
});

let _ = UnregisterClassW(class_name, None);

available
}
}

unsafe extern "system" fn window_proc(
window: HWND,
message: u32,
wparam: WPARAM,
lparam: LPARAM,
) -> LRESULT {
unsafe { DefWindowProcW(window, message, wparam, lparam) }
}

unsafe fn probe_window(window: HWND) -> Option<bool> {
unsafe {
let hdc = GetDC(Some(window));
if hdc.is_invalid() {
return None;
}

let available = probe_device_context(hdc);

ReleaseDC(Some(window), hdc);

available
}
}

unsafe fn probe_device_context(hdc: HDC) -> Option<bool> {
unsafe {
let pixel_format_descriptor = PIXELFORMATDESCRIPTOR {
nSize: std::mem::size_of::<PIXELFORMATDESCRIPTOR>() as u16,
nVersion: 1,
dwFlags: PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER,
iPixelType: PFD_TYPE_RGBA,
cColorBits: 32,
cDepthBits: 24,
iLayerType: PFD_MAIN_PLANE.0 as u8,
..Default::default()
};

let pixel_format = ChoosePixelFormat(hdc, &pixel_format_descriptor);
if pixel_format == 0 {
return None;
}
SetPixelFormat(hdc, pixel_format, &pixel_format_descriptor).ok()?;

let context = wglCreateContext(hdc).ok()?;

// Restore whatever was current before, so that probing from a thread that already
// renders through WGL is harmless.
let previous_context = wglGetCurrentContext();
let previous_hdc = wglGetCurrentDC();

let available = wglMakeCurrent(hdc, context).is_ok().then(|| {
let address = wglGetProcAddress(s!("glCreateShader")).map_or(0, |entry| entry as usize);
let _ = wglMakeCurrent(previous_hdc, previous_context);
// Besides null, some drivers report a missing entry point as 1, 2, 3 or -1.
!matches!(address, 0 | 1 | 2 | 3 | usize::MAX)
});

let _ = wglDeleteContext(context);

available
}
}

#[test]
fn probe_runs() {
// The answer depends on the machine, so this only covers the probe running to completion.
opengl_2_available();
}
Loading