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
64 changes: 64 additions & 0 deletions tests/test_util_default_device.py
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,70 @@ 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"}

# 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()
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()

Expand Down
30 changes: 24 additions & 6 deletions wgpu/utils/device.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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
Expand All @@ -74,9 +75,12 @@ 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.
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.
preferred_features (set of str): the features (extensions) that you want but do not strictly need.
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 (set of str): the features (extensions) that you need.
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.
"""
Expand All @@ -98,6 +102,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

Expand All @@ -108,6 +114,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),
]:
Expand All @@ -127,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())
Expand Down Expand Up @@ -195,12 +205,20 @@ 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()) & 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


Expand Down
Loading