Merge pull request #4607 from VoiceLessQ/fix/4150-keep-hardware-opengl

Windows: keep OpenGL for a GPU without Direct3D 12 and for a named adapter (#4150)
This commit is contained in:
BFlat
2026-10-29 23:35:55 +07:00
committed by GitHub
3 changed files with 55 additions and 15 deletions
+29 -14
View File
@@ -140,33 +140,48 @@ pub fn backends(default: Backends, env_set: bool, opengl: bool) -> Backends {
}
/// Whether a Windows start lists OpenGL's adapters beside Direct3D 12's: only when it leaves an
/// adapter out (an earlier start crashed on it, or a restart failed on it) or DX12 lists none
/// (Windows 7). wgpu's OpenGL backend keeps a hidden window on a thread that pumps no messages,
/// and Windows waits on it when the input language changes, so the whole app hung on a switch to
/// another keyboard layout (#4150, gfx-rs/wgpu#10504), even drawing with DX12.
/// adapter out (an earlier start crashed on it, or a restart failed on it), when
/// `WGPU_ADAPTER_NAME` names one (it may be an OpenGL adapter), or when DX12 lists no hardware
/// adapter (Windows 7, or a GPU without a DX12 driver, which then draws through OpenGL as before
/// rather than on the CPU with WARP). wgpu's OpenGL backend keeps a hidden window on a thread that
/// pumps no messages, and Windows waits on it when the input language changes, so the whole app
/// hung on a switch to another keyboard layout (#4150, gfx-rs/wgpu#10504), even drawing with DX12.
pub fn windows_opengl(left_out: &LeftOut) -> bool {
left_out.opengl_only || !left_out.adapters.is_empty() || !skipped().is_empty() || !dx12_lists_adapters()
opengl_needed(left_out, !skipped().is_empty(), std::env::var_os(NAME_ENV).is_some(), dx12_lists_hardware)
}
/// Whether Direct3D 12 lists any adapter, WARP's software one included. A driver that crashes here
/// crashes a start while it lists the adapters, which the next start answers with OpenGL alone.
/// [`windows_opengl`], with a restart leaving adapters out (`skipping`) and an adapter named
/// (`named`): `dx12_hardware` is asked only when nothing else decides.
fn opengl_needed(left_out: &LeftOut, skipping: bool, named: bool, dx12_hardware: impl FnOnce() -> bool) -> bool {
left_out.opengl_only || !left_out.adapters.is_empty() || skipping || named || !dx12_hardware()
}
/// Whether Direct3D 12 lists a hardware adapter; WARP, its software one, doesn't count. A driver
/// that crashes here crashes a start while it lists the adapters, which the next start answers with
/// OpenGL alone.
#[cfg(windows)]
fn dx12_lists_adapters() -> bool {
fn dx12_lists_hardware() -> bool {
let mut descriptor = wgpu::InstanceDescriptor::new_without_display_handle();
descriptor.backends = Backends::DX12;
// FXC is part of Windows, as for the window's own instance (#1194).
descriptor.backend_options.dx12.shader_compiler = wgpu::Dx12Compiler::Fxc;
// The shader compiler the window's own instance takes (#1194).
descriptor.backend_options.dx12.shader_compiler = wgpu::Dx12Compiler::from_env().unwrap_or(wgpu::Dx12Compiler::Fxc);
let instance = wgpu::Instance::new(descriptor);
let found = pollster::block_on(instance.enumerate_adapters(Backends::DX12)).len();
log::info!("Direct3D 12 lists {found} graphics adapters");
found > 0
let kinds: Vec<DeviceType> = pollster::block_on(instance.enumerate_adapters(Backends::DX12)).iter().map(|a| a.get_info().device_type).collect();
log::info!("Direct3D 12 lists these graphics adapters: {kinds:?}");
any_hardware(&kinds)
}
#[cfg(not(windows))]
fn dx12_lists_adapters() -> bool {
fn dx12_lists_hardware() -> bool {
false
}
/// Is one of the adapters of these kinds hardware, not software such as WARP?
#[cfg(any(windows, test))]
fn any_hardware(kinds: &[DeviceType]) -> bool {
kinds.iter().any(|k| *k != DeviceType::Cpu)
}
/// The largest texture side to ask the device for, which bounds the window's surface: the
/// adapter's own limit when it is above eframe's `default` (8192 px), so a window larger than
/// that keeps its surface (#1459). Never below `default`, which is what was asked for before.
+25
View File
@@ -472,6 +472,31 @@ fn windows_lists_opengl_once_a_start_leaves_an_adapter_out() {
assert_eq!(listing.backends(backends(Backends::all(), false, true)), Backends::GL);
}
/// OpenGL stays where it drew before #4150's fix: a GPU without a Direct3D 12 driver (DX12 lists
/// WARP alone) and an adapter `WGPU_ADAPTER_NAME` names; a start with a DX12 GPU lists DX12 alone.
#[test]
fn windows_lists_opengl_without_a_dx12_gpu_or_with_an_adapter_named() {
let start = LeftOut::default();
let asked = std::cell::Cell::new(false);
let hardware = |found: bool| {
asked.set(true);
found
};
assert!(!opengl_needed(&start, false, false, || hardware(true)), "a DX12 GPU: DX12 alone (#4150)");
assert!(asked.replace(false));
assert!(opengl_needed(&start, false, false, || hardware(false)), "WARP alone: the GPU draws through OpenGL, not the CPU");
assert!(opengl_needed(&start, false, true, || hardware(true)), "a named adapter may be an OpenGL one");
assert!(opengl_needed(&start, true, false, || hardware(true)), "a restart left an adapter out");
let listing = LeftOut { opengl_only: true, ..LeftOut::default() };
asked.set(false);
assert!(opengl_needed(&listing, false, false, || hardware(true)));
assert!(!asked.get(), "DX12 isn't listed once OpenGL must be");
assert!(any_hardware(&[DeviceType::Cpu, DeviceType::IntegratedGpu]));
assert!(any_hardware(&[DeviceType::VirtualGpu]));
assert!(!any_hardware(&[DeviceType::Cpu]), "WARP");
assert!(!any_hardware(&[]), "Windows 7");
}
#[test]
fn power_preference_follows_the_environment_then_the_preference() {
assert_eq!(power_preference(None, None), AUTOMATIC);
+1 -1
View File
@@ -55,7 +55,7 @@ How the web shell (`apps/vectorcraft-web/src/web.rs`) differs from desktop:
The canvas is rasterized on the CPU (`vectorcraft-render`, vello_cpu); the GPU (wgpu, through eframe) only composites the canvas texture and draws the UI, so any GPU that can show the window will do. The desktop app picks the window's adapter itself (`apps/vectorcraft/src/gpu.rs`, eframe's `native_adapter_selector`) and logs every adapter it found and the one it uses.
- **Order:** adapters that report they can't present to the window are never tried; then the one `WGPU_ADAPTER_NAME` names; hardware before software (llvmpipe, WARP); the native backends (Vulkan, Metal, DX12) before OpenGL; with Automatic, the GPU the Wayland compositor draws with, then the GPU that drives a display, and among those the one that drives the primary display; then the power preference; then a GPU's DX12 adapter before its Vulkan one (Intel's Vulkan driver made the whole window flicker black, #545), keeping the system's order among equals.
- **Windows backends:** Direct3D 12. OpenGL is listed beside it only when a start leaves an adapter out (a crash or a failed restart) or Direct3D 12 lists no adapter (Windows 7): wgpu's OpenGL backend keeps a hidden window whose thread pumps no messages, and the app hung on a switch to another input language (#4150, gfx-rs/wgpu#10504). Vulkan isn't started at all unless `WGPU_BACKEND` asks for it (`WGPU_BACKEND=vulkan`): creating a Vulkan instance loads every installed Vulkan driver into the process, and a faulty one (Intel's `igvk64.dll`, #806) crashed the app before its window appeared. After a start that crashed while it listed the Direct3D 12 adapters, later starts list OpenGL's alone (see the crash bullet below).
- **Windows backends:** Direct3D 12. OpenGL is listed beside it only when a start leaves an adapter out (a crash or a failed restart), when `WGPU_ADAPTER_NAME` names an adapter, or when Direct3D 12 lists no hardware adapter (Windows 7, or a GPU without a Direct3D 12 driver, which then draws through OpenGL rather than on the CPU with WARP): wgpu's OpenGL backend keeps a hidden window whose thread pumps no messages, and the app hung on a switch to another input language (#4150, gfx-rs/wgpu#10504). Vulkan isn't started at all unless `WGPU_BACKEND` asks for it (`WGPU_BACKEND=vulkan`): creating a Vulkan instance loads every installed Vulkan driver into the process, and a faulty one (Intel's `igvk64.dll`, #806) crashed the app before its window appeared. After a start that crashed while it listed the Direct3D 12 adapters, later starts list OpenGL's alone (see the crash bullet below).
- **The GPU that drives the display.** A GPU without a monitor is the wrong one even when it says it can show the window: on a desktop with a Ryzen's integrated GPU (no monitor) and an NVIDIA card driving both monitors, power saving picked the integrated GPU, Windows had to present every frame across adapters, and the integrated GPU's driver reset under that: the graphics device was lost, or the desktop's compositor went down and every monitor stayed black for minutes (pdfcraft#378). So Automatic asks the system which GPU drives which display and puts that GPU first; the power preference only orders the rest, and decides alone where the system can't say.
- **Windows:** `EnumDisplayDevices` (the display devices attached to the desktop, and the primary one), through the `winsafe` crate; the PCI ids in each device id are matched against wgpu's adapters. The registry doesn't tell a user which adapter drives a display, and WMI reports a resolution for a GPU without a monitor, so neither is used.
- **Linux:** the connected connectors in `/sys/class/drm` and their cards' PCI ids; a built-in panel (`eDP`, `LVDS`, `DSI`) counts as the primary display.