From 7466cb805b0540c2d9fc901c8faf4706570810f1 Mon Sep 17 00:00:00 2001 From: Almar Klein Date: Wed, 24 Jun 2026 11:14:22 +0200 Subject: [PATCH 1/5] Add preferred features for preconfigure_default_device --- tests/test_util_default_device.py | 52 +++++++++++++++++++++++++++++++ wgpu/utils/device.py | 19 +++++++++-- 2 files changed, 69 insertions(+), 2 deletions(-) diff --git a/tests/test_util_default_device.py b/tests/test_util_default_device.py index d4ebaef7..165102a2 100644 --- a/tests/test_util_default_device.py +++ b/tests/test_util_default_device.py @@ -222,6 +222,58 @@ def test_default_device_configure_required_features(caplog): helper.preconfigure_default_device("test", required_features={"shader-f16"}) +def test_default_device_configure_preferred_features(caplog): + + # This is normal + + helper = DefaultDeviceHelper() + helper.preconfigure_default_device("test", required_features={"float32-filterable"}) + device = helper.get_default_device() + assert device.features == {"float32-filterable"} + + # This does not work; not a standard feature + + helper = DefaultDeviceHelper() + with pytest.raises(ValueError): + helper.preconfigure_default_device( + "test", required_features={"texture-format16bit-norm"} + ) + + # preferred features to the rescue + + helper = DefaultDeviceHelper() + helper.preconfigure_default_device( + "test", preferred_features={"texture-format16bit-norm"} + ) + device = helper.get_default_device() + assert device.features == {"texture-format16bit-norm"} + + # Another variant + + helper = DefaultDeviceHelper() + helper.preconfigure_default_device( + "test", + preferred_features={ + "float32-filterable", + "texture-format16bit-norm", + "not-actuallt-a-feature", + }, + ) + device = helper.get_default_device() + assert device.features == {"float32-filterable", "texture-format16bit-norm"} + + # A pattern for pygfx + helper = DefaultDeviceHelper() + helper.preconfigure_default_device( + "test", preferred_features={"texture-formats-tier1", "texture-format16bit-norm"} + ) + device = helper.get_default_device() + # At least one should be active + assert device.features & {"texture-formats-tier1", "texture-format16bit-norm"} + # Its currently this one, but this will likely change, see https://github.com/gfx-rs/wgpu/issues/8122 + assert device.features == {"texture-format16bit-norm"} + + def test_default_device_configure_required_limits(caplog): helper = DefaultDeviceHelper() diff --git a/wgpu/utils/device.py b/wgpu/utils/device.py index d0a52274..1e389ae5 100644 --- a/wgpu/utils/device.py +++ b/wgpu/utils/device.py @@ -44,6 +44,7 @@ def preconfigure_default_device( adapter: GPUAdapter | None = None, # Device arguments label: str | None = None, + preferred_features: set[str] | None = None, required_features: set[enums.FeatureNameEnum] | None = None, required_limits: dict[str, int | None] | None = None, # default_queue: structs.QueueDescriptorStruct | None = None, @@ -58,7 +59,7 @@ def preconfigure_default_device( use wgpu can each require the features they need. For required features the union of set features is used. For required limits the minimum of each set limit is used. For the other arguments, the last set value is - used, and a warning is logged when a value is overriden. + used, and a warning is logged when a value is overridden. Arguments: caller_info (str): A very brief description of the code that calls @@ -74,6 +75,8 @@ def preconfigure_default_device( Setting the adapter overrules all other adapter settings (feature_level, power_preference, force_fallback_adapter, canvas). label (str): A human-readable label for the device. + preferred_features (list or str): the features (extensions) that you want but do not strictly need. + Check ``device.features`` for its success. Native features are allowed too. required_features (list of str): the features (extensions) that you need. Features can also be discarded by prefixing them with '!'. This is not recommended unless for testing and very specific use-cases. @@ -98,6 +101,8 @@ def preconfigure_default_device( if isinstance(required_features, (tuple, list)): required_features = set(required_features) + if isinstance(preferred_features, (tuple, list)): + preferred_features = set(preferred_features) ak, dk = self._adapter_kwargs, self._device_kwargs @@ -108,6 +113,7 @@ def preconfigure_default_device( (ak, "canvas", canvas, None, None), (ak, "adapter", adapter, GPUAdapter, None), (dk, "label", label, str, None), + (dk, "preferred_features", preferred_features, set, None), (dk, "required_features", required_features, set, enums.FeatureName), (dk, "required_limits", required_limits, dict, None), ]: @@ -195,12 +201,21 @@ def get_default_device(self) -> GPUDevice: The default device can be configured at import-time using ``preconfigure_default_device()``. """ if self._the_device is None: + # Get adapter adapter: GPUAdapter = self._adapter_kwargs.pop("adapter", None) if adapter is None: adapter = wgpu.gpu.request_adapter_sync(**self._adapter_kwargs) - self._the_device = adapter.request_device_sync(**self._device_kwargs) + # Handle preferred features + kwargs = self._device_kwargs.copy() + required_features = kwargs.get("required_features", set()) + extra_features = kwargs.pop("preferred_features", set()) + extra_features = {f for f in extra_features if f in adapter.features} + kwargs["required_features"] = required_features | extra_features + # Create device + self._the_device = adapter.request_device_sync(**kwargs) self._adapter_kwargs.clear() self._device_kwargs.clear() + return self._the_device From 4179b81e827da83024cd11c509a6fd04d263070e Mon Sep 17 00:00:00 2001 From: Almar Klein Date: Wed, 24 Jun 2026 15:12:20 +0200 Subject: [PATCH 2/5] use intersect --- wgpu/utils/device.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/wgpu/utils/device.py b/wgpu/utils/device.py index 1e389ae5..77ff2f0f 100644 --- a/wgpu/utils/device.py +++ b/wgpu/utils/device.py @@ -208,8 +208,7 @@ def get_default_device(self) -> GPUDevice: # Handle preferred features kwargs = self._device_kwargs.copy() required_features = kwargs.get("required_features", set()) - extra_features = kwargs.pop("preferred_features", set()) - extra_features = {f for f in extra_features if f in adapter.features} + extra_features = kwargs.pop("preferred_features", set()) & adapter.features kwargs["required_features"] = required_features | extra_features # Create device self._the_device = adapter.request_device_sync(**kwargs) From 9e600e4cdb0696b36bfac679d0d4f679848d9cb0 Mon Sep 17 00:00:00 2001 From: Almar Klein Date: Wed, 24 Jun 2026 15:12:52 +0200 Subject: [PATCH 3/5] Apply suggestion from @Vipitis Co-authored-by: Jan --- wgpu/utils/device.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wgpu/utils/device.py b/wgpu/utils/device.py index 77ff2f0f..4e5b8b40 100644 --- a/wgpu/utils/device.py +++ b/wgpu/utils/device.py @@ -75,7 +75,7 @@ def preconfigure_default_device( Setting the adapter overrules all other adapter settings (feature_level, power_preference, force_fallback_adapter, canvas). label (str): A human-readable label for the device. - preferred_features (list or str): the features (extensions) that you want but do not strictly need. + preferred_features (set of str): the features (extensions) that you want but do not strictly need. Check ``device.features`` for its success. Native features are allowed too. required_features (list of str): the features (extensions) that you need. Features can also be discarded by prefixing them with '!'. This is not recommended From cf7fdad2e0b6358345f4b64ed948d116cee3a495 Mon Sep 17 00:00:00 2001 From: Almar Klein Date: Wed, 24 Jun 2026 21:23:13 +0200 Subject: [PATCH 4/5] Apply suggestion from @almarklein --- wgpu/utils/device.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wgpu/utils/device.py b/wgpu/utils/device.py index 4e5b8b40..98061ac2 100644 --- a/wgpu/utils/device.py +++ b/wgpu/utils/device.py @@ -77,7 +77,7 @@ def preconfigure_default_device( label (str): A human-readable label for the device. preferred_features (set of str): the features (extensions) that you want but do not strictly need. Check ``device.features`` for its success. Native features are allowed too. - required_features (list of str): the features (extensions) that you need. + required_features (set of str): the features (extensions) that you need. Features can also be discarded by prefixing them with '!'. This is not recommended unless for testing and very specific use-cases. required_limits (dict): the various limits that you want to apply. From 1e494d164077a558409a72570d7ac31ffefe7a30 Mon Sep 17 00:00:00 2001 From: Almar Klein Date: Thu, 25 Jun 2026 09:14:01 +0200 Subject: [PATCH 5/5] tweak docs and error message --- tests/test_util_default_device.py | 12 ++++++++++++ wgpu/utils/device.py | 12 ++++++++---- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/tests/test_util_default_device.py b/tests/test_util_default_device.py index 165102a2..793b2e35 100644 --- a/tests/test_util_default_device.py +++ b/tests/test_util_default_device.py @@ -248,6 +248,18 @@ def test_default_device_configure_preferred_features(caplog): device = helper.get_default_device() assert device.features == {"texture-format16bit-norm"} + # Dropping also works + + helper = DefaultDeviceHelper() + helper.preconfigure_default_device( + "test", preferred_features={"texture-format16bit-norm"} + ) + helper.preconfigure_default_device( + "test", preferred_features={"!texture-format16bit-norm"} + ) + device = helper.get_default_device() + assert device.features == set() + # Another variant helper = DefaultDeviceHelper() diff --git a/wgpu/utils/device.py b/wgpu/utils/device.py index 77ff2f0f..2ba289c5 100644 --- a/wgpu/utils/device.py +++ b/wgpu/utils/device.py @@ -76,10 +76,11 @@ def preconfigure_default_device( (feature_level, power_preference, force_fallback_adapter, canvas). label (str): A human-readable label for the device. preferred_features (list or str): the features (extensions) that you want but do not strictly need. - Check ``device.features`` for its success. Native features are allowed too. + Check ``device.features`` for its success. Backend-specific / native features are allowed too. + Preferred features can also be discarded by prefixing them with '!'. required_features (list of str): the features (extensions) that you need. - Features can also be discarded by prefixing them with '!'. This is not recommended - unless for testing and very specific use-cases. + Required features can also be discarded by prefixing them with '!'. This is not recommended + except for testing and very specific use-cases. Only official features (from ``wgpu.FeatureName``) are allowed. required_limits (dict): the various limits that you want to apply. Limits can also be discarded by setting their value to None. """ @@ -133,8 +134,11 @@ def preconfigure_default_device( for value in values: value = value.lstrip("!") if value not in arg_values: + tip = "" + if arg_name == "required_features": + tip = f" If {value!r} is a native feature, use it in preferred_features instead." raise ValueError( - f"preconfigure_default_device ({caller_info}): {what} must be a one of {set(arg_values)}, but got {value!r}." + f"preconfigure_default_device ({caller_info}): {what} must be a one of {arg_values}, but got {value!r}.{tip}" ) if isinstance(arg_value, set): cur_value = arg_dict.setdefault(arg_name, set())