#12706·slint

Windows: automatically select the software renderer if OpenGL 2.0 is not available

Author: ancwrd1Created Jul 30, 2026Updated Sep 14, 2026
Labelsa:backend-winit

Feature Description

Rationale: in virtual environments, Windows does not always have OpenGL 2.0 drivers, but because Slint always selects femtovg as a default renderer, the application crashes. Basically, we have the following code in every application, platform-gated for Windows only, to do the manual selection based on OpenGL 2.0 availability.

rust
pub fn init_gui_backend() -> anyhow::Result<()> {
    if let Ok(backend) = env::var("SLINT_BACKEND") {
        debug!("Using pre-selected rendering backend: {backend}");
        slint::BackendSelector::new().select()?;
        return Ok(());
    }

    let renderer = if has_opengl_support() { "femtovg" } else { "software" };
    debug!("Using renderer: {renderer}");

    if let Err(err) = slint::BackendSelector::new().renderer_name(renderer.into()).select() {
        warn!("Failed to select rendering backend {renderer}: {err}; falling back to default");
        slint::BackendSelector::new().select()?;
    }
    Ok(())
}

fn has_opengl_support() -> bool {
    unsafe {
        if LoadLibraryW(w!("opengl32.dll")).is_err() {
            return false;
        }
        probe_gl_function()
    }
}

unsafe fn probe_gl_function() -> bool {
    unsafe {
        let class_name = s!("SlintGLProbe");
        let wc = WNDCLASSA {
            lpfnWndProc: mem::transmute::<usize, WNDPROC>(DefWindowProcA as *const () as usize),
            lpszClassName: class_name,
            ..Default::default()
        };
        RegisterClassA(&wc);

        let hwnd = CreateWindowExA(
            WINDOW_EX_STYLE(0),
            class_name,
            s!(""),
            WS_OVERLAPPED,
            0,
            0,
            1,
            1,
            Some(HWND::default()),
            None,
            None,
            None,
        );
        let Ok(hwnd) = hwnd else { return false };
        if hwnd.is_invalid() {
            return false;
        }

        let hdc = GetDC(Some(hwnd));
        let mut result = false;

        let pfd = PIXELFORMATDESCRIPTOR {
            nSize: 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, &pfd);
        if pixel_format != 0
            && SetPixelFormat(hdc, pixel_format, &pfd).is_ok()
            && let Ok(hglrc) = wglCreateContext(hdc)
        {
            if wglMakeCurrent(hdc, hglrc).is_ok() {
                let ptr = wglGetProcAddress(s!("glCreateShader"));
                // wglGetProcAddress returns 1/2/3/-1 as "not found" sentinels on
                // some drivers in addition to NULL. Treat all of those as missing.
                result = match ptr.map(|f| f as usize) {
                    None | Some(1 | 2 | 3 | usize::MAX) => false,
                    Some(_) => true,
                };
                let _ = wglMakeCurrent(hdc, HGLRC::default());
            }
            let _ = wglDeleteContext(hglrc);
        }

        ReleaseDC(Some(hwnd), hdc);
        let _ = DestroyWindow(hwnd);
        let _ = UnregisterClassA(class_name, None);

        result
    }
}

My suggestion is to add this to the library so that the backend selector takes Windows VMs into consideration.

Product Impact

No response