Skip to content
Open
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
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,12 @@ Bottom level categories:

- `naga::valid::ValidationError` is now always returned boxed, to avoid `clippy::large_result_err` warning. By @beicause in [#9612](https://github.com/gfx-rs/wgpu/pull/9612)

### Performance

#### General

- `Instance::request_adapter` no longer exposes every adapter in the system to select one. Backends can answer a request from cheap native descriptors (`wgpu_hal::Instance::request_adapter`), and the DX12 backend does: adapters are ranked via `IDXGIFactory6::EnumAdapterByGpuPreference` and only the selected adapter gets an `ID3D12Device` created. On machines with more than one GPU (hybrid-graphics laptops, desktops with an iGPU) this removes seconds of per-adapter driver initialization from startup. On DX12 systems with several adapters of the same device type, `LowPower`/`HighPerformance` ties now resolve in DXGI's GPU-preference order instead of enumeration order. By @AdrianEddy in [#10011](https://github.com/gfx-rs/wgpu/pull/10011).

### Bug Fixes

#### General
Expand Down
65 changes: 65 additions & 0 deletions wgpu-core/src/instance.rs
Original file line number Diff line number Diff line change
Expand Up @@ -487,6 +487,46 @@ impl Instance {
)
}

/// Apply the same validation and transforms to a lazily selected adapter
/// (`hal::Instance::request_adapter`) that [`Self::request_adapter`]
/// applies to a full enumeration. `None` means the adapter is unsuitable
/// and the caller must fall back to full enumeration.
///
/// Keep the filter sequence in sync with the per-backend loop in
/// [`Self::request_adapter`].
fn validate_requested_adapter(
&self,
mut raw: hal::DynExposedAdapter,
desc: &wgt::RequestAdapterOptions<&Surface>,
) -> Option<hal::DynExposedAdapter> {
if desc.force_fallback_adapter && raw.info.device_type != wgt::DeviceType::Cpu {
return None;
}

if let Some(surface) = desc.compatible_surface {
if let Err(err) = surface.get_capabilities_with_raw(&raw) {
log::debug!(
"Adapter {:?} not compatible with surface: {}",
raw.info,
err
);
return None;
}
}

self.adjust_limits_for_indirect_validation(&mut raw.capabilities.limits);
filter_features_and_limits(self.flags, &mut raw.features, &mut raw.capabilities.limits);
if !self.adapter_allowed(&raw) {
return None;
}

if desc.apply_limit_buckets {
limits::apply_limit_buckets(raw)
} else {
Some(raw)
}
}

pub fn enumerate_adapters(
self: &Arc<Self>,
backends: Backends,
Expand Down Expand Up @@ -562,6 +602,31 @@ impl Instance {
.compatible_surface
.and_then(|surface| surface.raw(backend));

// Give the backend a chance to select an adapter without exposing
// every adapter in the system (see `hal::Instance::request_adapter`).
// The result goes through the same validation as a full
// enumeration (`validate_requested_adapter` — keep it in sync with
// the filter sequence below); if it does not survive, fall through
// to the enumeration path so selection and error reporting are
// unchanged.
let lazy_adapter = unsafe {
instance.request_adapter(
desc.power_preference,
desc.force_fallback_adapter,
compatible_hal_surface,
)
};
if let Some(exposed) = lazy_adapter {
if let Some(exposed) = self.validate_requested_adapter(exposed, desc) {
log::debug!(
"Backend `{backend:?}` selected adapter without full enumeration: {:?}",
exposed.info
);
adapters.push(exposed);
continue;
}
}

let mut backend_adapters =
unsafe { instance.enumerate_adapters(compatible_hal_surface) };
if backend_adapters.is_empty() {
Expand Down
56 changes: 46 additions & 10 deletions wgpu-hal/src/auxil/dxgi/factory.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,19 @@ impl Deref for DxgiAdapter {
}
}

fn wrap_adapter(adapter1: Dxgi::IDXGIAdapter1) -> Option<DxgiAdapter> {
if !should_keep_adapter(&adapter1) {
return None;
}

if let Ok(adapter4) = adapter1.cast::<Dxgi::IDXGIAdapter4>() {
Some(DxgiAdapter::Adapter4(adapter4))
} else {
let adapter3 = adapter1.cast::<Dxgi::IDXGIAdapter3>().unwrap();
Some(DxgiAdapter::Adapter3(adapter3))
}
}

pub fn enumerate_adapters(factory: DxgiFactory) -> Vec<DxgiAdapter> {
let mut adapters = Vec::with_capacity(8);

Expand All @@ -93,21 +106,44 @@ pub fn enumerate_adapters(factory: DxgiFactory) -> Vec<DxgiAdapter> {
}
};

if !should_keep_adapter(&adapter1) {
continue;
}

if let Ok(adapter4) = adapter1.cast::<Dxgi::IDXGIAdapter4>() {
adapters.push(DxgiAdapter::Adapter4(adapter4));
} else {
let adapter3 = adapter1.cast::<Dxgi::IDXGIAdapter3>().unwrap();
adapters.push(DxgiAdapter::Adapter3(adapter3));
}
adapters.extend(wrap_adapter(adapter1));
}

adapters
}

/// [`enumerate_adapters`], ordered by `IDXGIFactory6::EnumAdapterByGpuPreference`
/// instead of `EnumAdapters1`'s primary-display-first order. Returns `None`
/// when DXGI 1.6 is unavailable (Windows 10 before 1803); the caller decides
/// how to rank adapters without OS preference ordering.
pub fn enumerate_adapters_by_gpu_preference(
factory: &DxgiFactory,
preference: Dxgi::DXGI_GPU_PREFERENCE,
) -> Option<Vec<DxgiAdapter>> {
let DxgiFactory::Factory6(factory6) = factory else {
return None;
};

let mut adapters = Vec::with_capacity(8);

for cur_index in 0.. {
profiling::scope!("IDXGIFactory6::EnumAdapterByGpuPreference");
let adapter1: Dxgi::IDXGIAdapter1 =
match unsafe { factory6.EnumAdapterByGpuPreference(cur_index, preference) } {
Ok(a) => a,
Err(e) if e.code() == Dxgi::DXGI_ERROR_NOT_FOUND => break,
Err(e) => {
log::error!("Failed enumerating adapters by GPU preference: {e}");
break;
}
};

adapters.extend(wrap_adapter(adapter1));
}

Some(adapters)
}

#[derive(Clone, Debug)]
pub enum DxgiFactory {
/// Provided by DXGI 1.4
Expand Down
86 changes: 73 additions & 13 deletions wgpu-hal/src/dx12/instance.rs
Original file line number Diff line number Diff line change
Expand Up @@ -171,19 +171,79 @@ impl crate::Instance for super::Instance {

adapters
.into_iter()
.filter_map(|raw| {
super::Adapter::expose(
raw,
&self.library,
&self.device_factory,
&self.dcomp_lib,
self.flags,
self.memory_budget_thresholds,
self.compiler_container.clone(),
self.options.clone(),
self.telemetry,
)
})
.filter_map(|raw| self.expose_adapter(raw))
.collect()
}

unsafe fn request_adapter(
&self,
power_preference: wgt::PowerPreference,
force_fallback_adapter: bool,
_surface_hint: Option<&super::Surface>,
) -> Option<crate::ExposedAdapter<super::Api>> {
// Ranking from DXGI descriptors alone, so only the selected adapter
// pays `D3D12CreateDevice`. `EnumAdapters1` order (the adapter the
// primary display is connected to first) mirrors what an unsorted
// full enumeration would select for `PowerPreference::None`. The
// preference modes need `EnumAdapterByGpuPreference` (DXGI 1.6);
// without it, decline so the caller's full-enumeration ranking runs.
let preference = match power_preference {
wgt::PowerPreference::None => None,
wgt::PowerPreference::LowPower => Some(Dxgi::DXGI_GPU_PREFERENCE_MINIMUM_POWER),
wgt::PowerPreference::HighPerformance => {
Some(Dxgi::DXGI_GPU_PREFERENCE_HIGH_PERFORMANCE)
}
};
let raw_adapters = match preference {
Some(preference) => auxil::dxgi::factory::enumerate_adapters_by_gpu_preference(
&self.factory,
preference,
)?,
None => auxil::dxgi::factory::enumerate_adapters(self.factory.clone()),
};

let mut raw_adapters: Vec<_> = raw_adapters
.into_iter()
.map(|raw| {
let is_software = unsafe { raw.GetDesc1() }.is_ok_and(|desc| {
Dxgi::DXGI_ADAPTER_FLAG(desc.Flags as i32)
.contains(Dxgi::DXGI_ADAPTER_FLAG_SOFTWARE)
});
(raw, is_software)
})
.collect();
if force_fallback_adapter {
raw_adapters.retain(|&(_, is_software)| is_software);
} else if preference.is_some() {
// `EnumAdapterByGpuPreference` does not pin the software
// rasterizer last, but the caller's ranking of a full enumeration
// does (`wgt::DeviceType::Cpu` sorts last). `PowerPreference::None`
// keeps raw `EnumAdapters1` order, which an unsorted full
// enumeration also uses.
raw_adapters.sort_by_key(|&(_, is_software)| is_software);
}

raw_adapters
.into_iter()
.find_map(|(raw, _)| self.expose_adapter(raw))
}
}

impl super::Instance {
fn expose_adapter(
&self,
raw: auxil::dxgi::factory::DxgiAdapter,
) -> Option<crate::ExposedAdapter<super::Api>> {
super::Adapter::expose(
raw,
&self.library,
&self.device_factory,
&self.dcomp_lib,
self.flags,
self.memory_budget_thresholds,
self.compiler_container.clone(),
self.options.clone(),
self.telemetry,
)
}
}
18 changes: 18 additions & 0 deletions wgpu-hal/src/dynamic/instance.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,13 @@ pub trait DynInstance: DynResource {
&self,
surface_hint: Option<&dyn DynSurface>,
) -> Vec<DynExposedAdapter>;

unsafe fn request_adapter(
&self,
power_preference: wgt::PowerPreference,
force_fallback_adapter: bool,
surface_hint: Option<&dyn DynSurface>,
) -> Option<DynExposedAdapter>;
}

impl<I: Instance + DynResource> DynInstance for I {
Expand Down Expand Up @@ -68,4 +75,15 @@ impl<I: Instance + DynResource> DynInstance for I {
})
.collect()
}

unsafe fn request_adapter(
&self,
power_preference: wgt::PowerPreference,
force_fallback_adapter: bool,
surface_hint: Option<&dyn DynSurface>,
) -> Option<DynExposedAdapter> {
let surface_hint = surface_hint.map(|s| s.expect_downcast_ref());
unsafe { I::request_adapter(self, power_preference, force_fallback_adapter, surface_hint) }
.map(Into::into)
}
}
18 changes: 18 additions & 0 deletions wgpu-hal/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -668,6 +668,24 @@ pub trait Instance: Sized + WasmNotSendSync {
&self,
surface_hint: Option<&<Self::A as Api>::Surface>,
) -> Vec<ExposedAdapter<Self::A>>;

/// Expose the single adapter that best matches `power_preference` and
/// `force_fallback_adapter`, exposing as few adapters as possible.
///
/// The default `None` means no lazy selection is available and the caller
/// falls back to [`Self::enumerate_adapters`]. Overrides must select
/// consistently with how callers rank a full enumeration
/// (`force_fallback_adapter` restricts the choice to
/// [`wgt::DeviceType::Cpu`]). The caller re-validates the result, so
/// `surface_hint` may be ignored.
unsafe fn request_adapter(
&self,
_power_preference: wgt::PowerPreference,
_force_fallback_adapter: bool,
_surface_hint: Option<&<Self::A as Api>::Surface>,
) -> Option<ExposedAdapter<Self::A>> {
None
}
}

pub trait Surface: WasmNotSendSync {
Expand Down