OpenTranscode/tests/test_gpu_profiles.py

203 lines
7.7 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 opentranscode.codec_profiles import VIDEO_CODECS
from opentranscode.encoder_worker import resolve_gpu_encoder
from opentranscode.gpu_profiles import (
GPU_PROFILES,
encoder_filter_chain,
encoder_quality_args,
encoder_pre_args,
gpu_profile_by_key,
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):
from opentranscode.codec_profiles import 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):
from opentranscode.source_builder import gpu_dep_packages
pkgs = gpu_dep_packages("nvenc", "arch")
assert "nv-codec-headers" in pkgs
def test_vaapi_qsv_driver_packages(self):
from opentranscode.source_builder import 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):
from opentranscode.source_builder import 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
from opentranscode.ui_window import 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")
def test_launcher_match_same_as_package(self, opentranscode_module):
from opentranscode.gpu_profiles import match_gpu_profile as pkg_match
assert opentranscode_module.match_gpu_profile("RTX 4090").key \
== pkg_match("RTX 4090").key
def test_launcher_worker_gpu_param(self, opentranscode_module):
import inspect
sig = inspect.signature(
opentranscode_module.SourceBuildWorker.__init__)
assert "gpu_profile_key" in sig.parameters