193 lines
7.3 KiB
Python
193 lines
7.3 KiB
Python
"""v4.8.0 GPU capability-profile tests.
|
|
|
|
Covers the combined generation dropdown entries: profile data
|
|
integrity, the auto-match matrix (including Tesla/data-center and the
|
|
crypto-era CMP oddballs), profile-driven encoder resolution, and the
|
|
rebuild dep-tree extension.
|
|
"""
|
|
import os
|
|
|
|
import pytest
|
|
|
|
from conftest import load_program_module
|
|
|
|
_m = load_program_module()
|
|
VIDEO_CODECS = _m.VIDEO_CODECS
|
|
resolve_gpu_encoder = _m.resolve_gpu_encoder
|
|
GPU_PROFILES = _m.GPU_PROFILES
|
|
encoder_filter_chain = _m.encoder_filter_chain
|
|
encoder_quality_args = _m.encoder_quality_args
|
|
encoder_pre_args = _m.encoder_pre_args
|
|
gpu_profile_by_key = _m.gpu_profile_by_key
|
|
match_gpu_profile = _m.match_gpu_profile
|
|
|
|
|
|
class TestProfileData:
|
|
def test_keys_unique(self):
|
|
keys = [p.key for p in GPU_PROFILES]
|
|
assert len(keys) == len(set(keys))
|
|
|
|
def test_labels_unique(self):
|
|
labels = [p.label for p in GPU_PROFILES]
|
|
assert len(labels) == len(set(labels))
|
|
|
|
def test_cpu_only_profile_claims_nothing(self):
|
|
cpu = gpu_profile_by_key("cpu")
|
|
assert cpu.encoders == {}
|
|
assert cpu.api == "none"
|
|
|
|
def test_compute_boards_claim_nothing(self):
|
|
"""V100/A100/H100-class boards and CMP 170HX have no NVENC —
|
|
selecting them must land on the CPU path, not crash."""
|
|
compute = gpu_profile_by_key("nv-compute")
|
|
assert compute.encoders == {}
|
|
|
|
def test_ada_has_av1_older_nvidia_does_not(self):
|
|
ada = gpu_profile_by_key("nv-ada")
|
|
assert ada.encoders["av1"] == "av1_nvenc"
|
|
for key in ("nv-pascal", "nv-turing", "nv-ampere"):
|
|
assert "av1" not in gpu_profile_by_key(key).encoders
|
|
|
|
def test_intel_arc_has_av1_qsv(self):
|
|
arc = gpu_profile_by_key("intel-arc")
|
|
assert arc.api == "qsv"
|
|
assert arc.encoders["av1"] == "av1_qsv"
|
|
|
|
def test_amd_rdna3_has_av1_vaapi_rdna12_does_not(self):
|
|
assert gpu_profile_by_key("amd-rdna3").encoders["av1"] == "av1_vaapi"
|
|
assert "av1" not in gpu_profile_by_key("amd-rdna12").encoders
|
|
|
|
def test_vp9_only_via_vaapi(self):
|
|
VIDEO_CODECS = load_program_module().VIDEO_CODECS
|
|
vp9 = next(c for c in VIDEO_CODECS if "VP9" in c.label)
|
|
assert vp9.gpu_encoders_by_api == {"vaapi": "vp9_vaapi"}
|
|
|
|
|
|
class TestMatchMatrix:
|
|
@pytest.mark.parametrize("name,expected_key", [
|
|
("NVIDIA GeForce GTX 1070", "nv-pascal"),
|
|
("NVIDIA GeForce GTX 1080 Ti", "nv-pascal"),
|
|
("NVIDIA GeForce RTX 2060", "nv-turing"),
|
|
("NVIDIA GeForce GTX 1660 SUPER", "nv-turing"),
|
|
("NVIDIA GeForce RTX 3060", "nv-ampere"),
|
|
("NVIDIA GeForce RTX 4090", "nv-ada"),
|
|
("NVIDIA GeForce RTX 5080", "nv-ada"),
|
|
("Tesla P40", "nv-pascal"),
|
|
("Tesla T4", "nv-turing"),
|
|
("NVIDIA A10", "nv-ampere"),
|
|
("NVIDIA CMP 50HX", "nv-turing"),
|
|
("NVIDIA CMP 90HX", "nv-ampere"),
|
|
("NVIDIA CMP 170HX GA100", "nv-compute"),
|
|
("NVIDIA CMP 170HX", "nv-compute"),
|
|
("NVIDIA A100-SXM4-40GB", "nv-compute"),
|
|
("NVIDIA H100", "nv-compute"),
|
|
("Intel(R) Arc(TM) B580", "intel-arc"),
|
|
("Intel(R) UHD Graphics 770", "intel-xe"),
|
|
])
|
|
def test_match(self, name, expected_key):
|
|
assert match_gpu_profile(name).key == expected_key
|
|
|
|
def test_no_match_returns_none(self):
|
|
assert match_gpu_profile("") is None
|
|
assert match_gpu_profile("Unknown Widget Graphics") is None
|
|
|
|
|
|
class TestProfileResolution:
|
|
def _env(self, functional, flags=None, profile_key=""):
|
|
class G:
|
|
pass
|
|
g = G()
|
|
g.functional = functional
|
|
g.profile_key = profile_key
|
|
|
|
class E:
|
|
pass
|
|
e = E()
|
|
e.gpu = g
|
|
e.av1an_flags = flags or {}
|
|
return e
|
|
|
|
def test_hevc_via_pascal_profile(self):
|
|
env = self._env({"hevc_nvenc": True}, {"gpu_profile": "nv-pascal"})
|
|
codec = next(c for c in VIDEO_CODECS if "x265" in c.label)
|
|
assert resolve_gpu_encoder("auto", codec, env) == ("hevc_nvenc", "nvenc")
|
|
|
|
def test_arc_qsv_hevc(self):
|
|
env = self._env({"hevc_qsv": True}, {"gpu_profile": "intel-arc"})
|
|
codec = next(c for c in VIDEO_CODECS if "x265" in c.label)
|
|
assert resolve_gpu_encoder("auto", codec, env) == ("hevc_qsv", "qsv")
|
|
|
|
def test_vp9_on_amd_stays_cpu(self):
|
|
# AMD VCN has no VP9 encode — even though ffmpeg has vp9_vaapi,
|
|
# the RDNA 3 profile truthfully claims none.
|
|
env = self._env({"vp9_vaapi": True}, {"gpu_profile": "amd-rdna3"})
|
|
codec = next(c for c in VIDEO_CODECS if "VP9" in c.label)
|
|
assert resolve_gpu_encoder("auto", codec, env) == (None, None)
|
|
|
|
def test_vaapi_encoder_args_shape(self):
|
|
profile = gpu_profile_by_key("amd-rdna3")
|
|
assert encoder_pre_args(profile)[0] == "-vaapi_device"
|
|
assert "format=nv12,hwupload" in encoder_filter_chain(profile)
|
|
args = encoder_quality_args("vaapi", "hevc_vaapi", 28, 9)
|
|
assert args[:2] == ["-rc_mode", "CQP"]
|
|
|
|
|
|
class TestRebuildGpuDeps:
|
|
def test_nvenc_needs_codec_headers(self):
|
|
gpu_dep_packages = load_program_module().gpu_dep_packages
|
|
pkgs = gpu_dep_packages("nvenc", "arch")
|
|
assert "nv-codec-headers" in pkgs
|
|
|
|
def test_vaapi_qsv_driver_packages(self):
|
|
gpu_dep_packages = load_program_module().gpu_dep_packages
|
|
assert any("libva" in p for p in gpu_dep_packages("vaapi", "arch"))
|
|
assert any("intel-media-driver" in p for p in gpu_dep_packages("qsv", "arch"))
|
|
|
|
def test_unpackaged_family_returns_empty(self):
|
|
gpu_dep_packages = load_program_module().gpu_dep_packages
|
|
assert gpu_dep_packages("nvenc", "nixos") == []
|
|
|
|
|
|
def importlib_pyside_available():
|
|
import importlib.util
|
|
return importlib.util.find_spec("PySide6") is not None
|
|
|
|
|
|
class TestGpuComboEngineInteraction:
|
|
@pytest.mark.skipif(
|
|
not importlib_pyside_available(),
|
|
reason="requires a real PySide6 (stub mode cannot instantiate Qt)",
|
|
)
|
|
def test_cpu_engine_disables_gpu_menu(self):
|
|
"""v4.8.1: ENGINE = CPU greys out the GPU dropdown; the explicit
|
|
'None (CPU-only encode)' entry remains for opting out while a
|
|
GPU-capable engine is selected."""
|
|
os.environ.setdefault("QT_QPA_PLATFORM", "offscreen")
|
|
from PySide6.QtWidgets import QApplication
|
|
|
|
OpenCodecMaster = load_program_module().OpenCodecMaster
|
|
|
|
app = QApplication.instance() or QApplication([])
|
|
window = OpenCodecMaster()
|
|
try:
|
|
gpu_idx = next(i for i in range(window.gpu_combo.count())
|
|
if "None (CPU-only" in window.gpu_combo.itemText(i))
|
|
assert gpu_idx > 0, "None (CPU-only) must be an explicit option"
|
|
|
|
window.engine_combo.setCurrentIndex(0) # Auto
|
|
assert window.gpu_combo.isEnabled()
|
|
window.engine_combo.setCurrentIndex(2) # CPU
|
|
assert not window.gpu_combo.isEnabled(), (
|
|
"ENGINE = CPU must disable the GPU dropdown"
|
|
)
|
|
window.engine_combo.setCurrentIndex(1) # GPU
|
|
assert window.gpu_combo.isEnabled()
|
|
finally:
|
|
window.close()
|
|
|
|
|
|
class TestLauncherParity:
|
|
def test_launcher_has_gpu_profiles(self, opentranscode_module):
|
|
assert hasattr(opentranscode_module, "GPU_PROFILES")
|
|
assert hasattr(opentranscode_module, "match_gpu_profile")
|