blender/tests/performance/api/test.py
Jeroen Bakker 64ac8ada59 Benchmark: Vulkan device selection
This PR allows to run benchmarks on multiple GPUs (one at a time).
It also contains some fixes to the reporting and normalization of the
device ID's.

Device ID normalization is done as some existing configs only contain
`VULKAN` which need to be matched against `VULKAN_0` and vise-versa.

Device selection doesn't use `blender --gpu-device help` to detect devices
as that option doesn't exist in older builds.

Now with fixed python validations.

Original Pull Request: https://projects.blender.org/blender/blender/pulls/158898

Pull Request: https://projects.blender.org/blender/blender/pulls/159234
2026-05-28 16:28:15 +02:00

118 lines
3.4 KiB
Python

# SPDX-FileCopyrightText: 2021-2022 Blender Authors
#
# SPDX-License-Identifier: Apache-2.0
import abc
import fnmatch
import typing
class Test:
@abc.abstractmethod
def name(self) -> str:
"""
Name of the test.
"""
@abc.abstractmethod
def category(self) -> str:
"""
Category of the test.
"""
def use_device(self) -> bool:
"""
Test uses a specific CPU or GPU device.
"""
return False
def supported_device_types(self) -> typing.List[str]:
"""
Supported device types when using multiple devices.
"""
return ['CPU']
def use_background(self) -> bool:
"""
Test runs in background mode and requires no display.
"""
return True
@staticmethod
def blender_gpu_arguments(device_id: str, gpu_backend: str) -> list:
"""
Return GPU arguments for blender.
Always includes --gpu-backend and optional include --gpu-device when device_id isn't
default (0 or missing).
"""
args = ['--gpu-backend', gpu_backend]
if '_' in device_id:
parts = device_id.rsplit('_', 1)
device_index = int(parts[1])
# Only specify --gpu-device for non-zero indices. Older builds could not support it.
if device_index > 0:
args += ['--gpu-device', str(device_index)]
return args
@abc.abstractmethod
def run(self, env, device_id: str, gpu_backend: str) -> dict:
"""
Execute the test and report results.
"""
class TestCollection:
def __init__(self, env, names_filter: list = ['*'], categories_filter: list = ['*'], background: bool = False):
import importlib
import pkgutil
import tests
self.tests = []
# Find and import all Python files in the tests folder, and generate
# the list of tests for each.
for _, modname, _ in pkgutil.iter_modules(tests.__path__, 'tests.'):
module = importlib.import_module(modname)
tests = module.generate(env)
for test in tests:
if background and not test.use_background():
continue
test_category = test.category()
found = False
for category_filter in categories_filter:
if fnmatch.fnmatch(test_category, category_filter):
found = True
if not found:
continue
test_name = test.name()
included = False
excluded = False
for name_filter in names_filter:
is_exclusion = name_filter.startswith('!')
pattern = name_filter[1:] if is_exclusion else name_filter
if fnmatch.fnmatch(test_name, pattern):
if is_exclusion:
excluded = True
break
else:
included = True
if not included or excluded:
continue
self.tests.append(test)
def find(self, test_name: str, test_category: str):
# Find a test based on name and category.
for test in self.tests:
if test.name() == test_name and test.category() == test_category:
return test
return None