cat / goq
clone

turns a linux box into a private steam streaming appliance: plug in, tap your key, play from another computer

Merge pull request #96 from FelineStateMachine/feat/uat-va-capability-probe

FelineStateMachine committed eb665a8
fix(uat): select the exact AMD VA encoder
commit
eb665a875e60bbfcb2736b74e4fe8318a81f3eb1
parent
9ade966c0d7e07b9a6aef5ce004ab67136977939
parent
f225f0b272aca3eaa9a8e11b201412bd07001f8a
author
FelineStateMachine <dami.etoile@gmail.com>
4 files changed, +540 -8
Mdocs/fresh-bazzite-host.md5+-
Mdocs/hardware-uat/README.md22++-
Mscripts/run-bazzite-hardware-uat.sh249+++++++++++++++++++++++++++-
Ascripts/tests/bazzite-hardware-uat-selection.sh272++++++++++++++++++++++++++++++
M docs/fresh-bazzite-host.md +4 -1
M docs/hardware-uat/README.md +21 -1
M scripts/run-bazzite-hardware-uat.sh +243 -6
@@ -2,14 +2,220 @@
22 33 set -euo pipefail44 5-if [[ $# -ne 3 ]]; then6- echo "usage: $0 UAT_ROOT UAT_COMMIT WORKFLOW_RUN_ID" >&25+GST_INSPECT_MAX_BYTES=10485766+7+run_bounded_gst_inspect() {8+ local timeout_path="$1"9+ local gst_inspect_path="$2"10+ local output_path="$3"11+ local output_size12+ shift 313+14+ if ! "$timeout_path" --signal=TERM --kill-after=2s 5s \15+ "$gst_inspect_path" "$@" 2>/dev/null \16+ | head -c "$((GST_INSPECT_MAX_BYTES + 1))" >"$output_path"17+ then18+ return 119+ fi20+ output_size="$(wc -c <"$output_path" | tr -d '[:space:]')"21+ [[ "$output_size" =~ ^[0-9]+$ && "$output_size" -le "$GST_INSPECT_MAX_BYTES" ]]22+}23+24+gst_property_block_contains() {25+ local property="$1"26+ local needle="$2"27+28+ awk -v property="$property" -v needle="$needle" '29+ $0 ~ ("^ " property "[[:space:]]+:") {30+ inside = 131+ }32+ inside && index($0, needle) {33+ found = 134+ }35+ inside && $0 !~ ("^ " property "[[:space:]]+:") \36+ && $0 ~ /^ [^[:space:]].*[[:space:]]+:/ {37+ exit38+ }39+ END { exit !found }40+ '41+}42+43+gst_property_block_is_writable() {44+ local property="$1"45+46+ awk -v property="$property" '47+ $0 ~ ("^ " property "[[:space:]]+:") {48+ inside = 149+ }50+ inside && index(tolower($0), "writable") {51+ found = 152+ }53+ inside && $0 !~ ("^ " property "[[:space:]]+:") \54+ && $0 ~ /^ [^[:space:]].*[[:space:]]+:/ {55+ exit56+ }57+ END { exit !found }58+ '59+}60+61+gstva_factory_supports_uat() {62+ local inspection="$1"63+ local property64+65+ gst_property_block_contains rate-control cbr <<<"$inspection" || return 166+ gst_property_block_contains rate-control cqp <<<"$inspection" || return 167+ for property in \68+ aud b-frames key-int-max rate-control ref-frames target-usage bitrate qpi qpp69+ do70+ gst_property_block_is_writable "$property" <<<"$inspection" || return 171+ done72+}73+74+select_amd_gstva_h264_encoder() {75+ local gst_inspect_path="$1"76+ local sysfs_root="${2:-/sys/class/drm}"77+ local device_root="${3:-/dev/dri}"78+ local require_character="${4:-true}"79+ local override_node="${5:-}"80+ local override_factory="${6:-}"81+ local timeout_path="${7:?timeout command is required}"82+ local output_root="${8:?bounded inspection output directory is required}"83+ local factory84+ local factories85+ local factory_output86+ local inspection87+ local node_name88+ local sysfs_node89+ local driver90+ local candidate91+ local pair92+ local candidate_count=093+94+ render_node=''95+ va_encoder=''96+ va_candidates=''97+ [[ -d "$output_root" && ! -L "$output_root" ]] || return 198+ if ! LC_ALL=C run_bounded_gst_inspect \99+ "$timeout_path" "$gst_inspect_path" "$output_root/gst-inspect.registry";100+ then101+ return 1102+ fi103+ factories="$(awk '104+ $2 ~ /^va(renderD[0-9]+)?h264(lp)?enc:$/ {105+ sub(/:$/, "", $2)106+ print $2107+ }108+ ' "$output_root/gst-inspect.registry" | sort -u)" || return 1109+110+ for sysfs_node in "$sysfs_root"/renderD*; do111+ [[ -e "$sysfs_node/device/driver" ]] || continue112+ node_name="$(basename "$sysfs_node")"113+ [[ "$node_name" =~ ^renderD[0-9]+$ ]] || continue114+ driver="$(basename "$(readlink -f "$sysfs_node/device/driver")")" || continue115+ [[ "$driver" == amdgpu ]] || continue116+ candidate="$device_root/$node_name"117+ [[ -e "$candidate" && ! -L "$candidate" && -r "$candidate" && -w "$candidate" ]] \118+ || continue119+ if [[ "$require_character" == true && ! -c "$candidate" ]]; then120+ continue121+ fi122+123+ while IFS= read -r factory; do124+ [[ -n "$factory" ]] || continue125+ factory_output="$output_root/gst-inspect.$factory"126+ if ! LC_ALL=C run_bounded_gst_inspect \127+ "$timeout_path" "$gst_inspect_path" "$factory_output" "$factory";128+ then129+ continue130+ fi131+ inspection="$(<"$factory_output")"132+ gst_property_block_contains device-path "Default: \"$candidate\"" \133+ <<<"$inspection" || continue134+ gstva_factory_supports_uat "$inspection" || continue135+ pair="$candidate $factory"136+ if [[ -n "$va_candidates" ]]; then137+ va_candidates="$va_candidates138+$pair"139+ else140+ va_candidates="$pair"141+ fi142+ candidate_count=$((candidate_count + 1))143+ if [[ -n "$override_node" && "$candidate" == "$override_node" \144+ && "$factory" == "$override_factory" ]]; then145+ render_node="$candidate"146+ va_encoder="$factory"147+ elif [[ -z "$override_node" && "$candidate_count" -eq 1 ]]; then148+ render_node="$candidate"149+ va_encoder="$factory"150+ fi151+ done <<<"$factories"152+ done153+154+ if [[ -n "$override_node" ]]; then155+ [[ -n "$render_node" && -n "$va_encoder" ]]156+ return157+ fi158+ if [[ "$candidate_count" -eq 1 ]]; then159+ return 0160+ fi161+ render_node=''162+ va_encoder=''163+ [[ "$candidate_count" -eq 0 ]] && return 1164+ return 2165+}166+167+if [[ "${SIGIL_HARDWARE_UAT_SOURCE_ONLY:-}" == 1 ]]; then168+ if [[ "${BASH_SOURCE[0]}" != "$0" ]]; then169+ return 0170+ fi171+ exit 0172+fi173+174+usage() {175+ echo "usage: $0 UAT_ROOT UAT_COMMIT WORKFLOW_RUN_ID [--render-node PATH --va-encoder FACTORY]" >&2176+}177+178+if [[ $# -lt 3 ]]; then179+ usage7180 exit 648181 fi9182 10183 uat_root="$1"11184 uat_commit="$2"12185 workflow_run_id="$3"186+shift 3187+render_node_override=''188+va_encoder_override=''189+render_node_override_set=false190+va_encoder_override_set=false191+while [[ $# -gt 0 ]]; do192+ case "$1" in193+ --render-node)194+ [[ $# -ge 2 && "$render_node_override_set" == false && -n "$2" ]] \195+ || { usage; exit 64; }196+ render_node_override="$2"197+ render_node_override_set=true198+ shift 2199+ ;;200+ --va-encoder)201+ [[ $# -ge 2 && "$va_encoder_override_set" == false && -n "$2" ]] \202+ || { usage; exit 64; }203+ va_encoder_override="$2"204+ va_encoder_override_set=true205+ shift 2206+ ;;207+ *)208+ usage209+ exit 64210+ ;;211+ esac212+done213+if [[ "$render_node_override_set" == true || "$va_encoder_override_set" == true ]]; then214+ [[ "$render_node_override_set" == true && "$va_encoder_override_set" == true ]] || {215+ echo "--render-node and --va-encoder must be provided together" >&2216+ exit 64217+ }218+fi13219 short_commit="${uat_commit:0:12}"14220 15221 [[ "$uat_commit" =~ ^[0-9a-f]{40}$ ]] || {
@@ -205,11 +411,39 @@ pw_link="$(command -v pw-link)"
205411 xprop="$(command -v xprop)"206412 gst_launch="$(command -v gst-launch-1.0)"207413 gst_inspect="$(command -v gst-inspect-1.0)"414+timeout_command="$(command -v timeout)"208415 ffmpeg="$(command -v ffmpeg)"209-render_node=/dev/dri/renderD128210-va_encoder=vah264enc211-[[ -S "$XDG_RUNTIME_DIR/pipewire-0" && -r "$render_node" && -w "$render_node" ]]212-"$gst_inspect" "$va_encoder" >/dev/null416+[[ -S "$XDG_RUNTIME_DIR/pipewire-0" ]]417+selection_status=0418+select_amd_gstva_h264_encoder \419+ "$gst_inspect" /sys/class/drm /dev/dri true \420+ "$render_node_override" "$va_encoder_override" \421+ "$timeout_command" "$private" || selection_status=$?422+if [[ "$selection_status" -ne 0 ]]; then423+ if [[ -n "$render_node_override" ]]; then424+ echo "the requested render-node and VA-encoder pair is not eligible for this UAT" >&2425+ elif [[ "$selection_status" -eq 2 ]]; then426+ echo "multiple AMD GstVA H.264 encoder pairs are eligible; select one exact pair with --render-node and --va-encoder" >&2427+ else428+ echo "no accessible AMD render node has a GstVA H.264 factory satisfying the CBR and CQP UAT contract" >&2429+ fi430+ if [[ -n "$va_candidates" ]]; then431+ while IFS= read -r pair; do432+ printf 'eligible_pair=%s\n' "$pair" >&2433+ done <<<"$va_candidates"434+ fi435+ exit 1436+fi437+if [[ -n "$render_node_override" ]]; then438+ vaapi_selection_mode=explicit439+else440+ vaapi_selection_mode=automatic-unique441+fi442+{443+ echo "vaapi_render_node=$render_node"444+ echo "vaapi_encoder=$va_encoder"445+ echo "vaapi_selection_mode=$vaapi_selection_mode"446+} >"$private/vaapi-selection.env"213447 214448 cat >"$fixed_config" <<EOF215449 identity_path = "$host_identity"
@@ -812,6 +1046,9 @@ fi
8121046 echo "fixed_observed_fps=$fixed_observed_fps"8131047 echo external_cqp_minimum_fps=558141048 echo "external_cqp_observed_fps=$external_cqp_observed_fps"1049+ echo "vaapi_render_node=$render_node"1050+ echo "vaapi_encoder=$va_encoder"1051+ echo "vaapi_selection_mode=$vaapi_selection_mode"8151052 echo cross_state_capture_contention=rejected8161053 echo gamescope_pointer_restart_cycles=28171054 echo gamescope_pointer_feedback_recovery=pass
A scripts/tests/bazzite-hardware-uat-selection.sh +272 -0
@@ -0,0 +1,272 @@
1+#!/usr/bin/env bash2+3+set -euo pipefail4+5+script_dir="$(CDPATH='' cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)"6+runner="$script_dir/../run-bazzite-hardware-uat.sh"7+temp_parent="${TMPDIR:-/tmp}"8+temp_parent="${temp_parent%/}"9+temp_root="$(mktemp -d "$temp_parent/sigil-hardware-selection.XXXXXX")"10+11+cleanup() {12+ local status=$?13+ trap - EXIT INT TERM HUP14+ case "$temp_root" in15+ "$temp_parent"/sigil-hardware-selection.??????) rm -rf -- "$temp_root" ;;16+ esac17+ exit "$status"18+}19+trap cleanup EXIT INT TERM HUP20+21+export SIGIL_HARDWARE_UAT_SOURCE_ONLY=122+# shellcheck source=../run-bazzite-hardware-uat.sh23+# shellcheck disable=SC109124+source "$runner"25+render_node=''26+va_encoder=''27+va_candidates=''28+29+fail() {30+ printf 'FAIL: %s\n' "$1" >&231+ exit 132+}33+34+fixture="$temp_root/fixture"35+sysfs_root="$fixture/sys/class/drm"36+device_root="$fixture/dev/dri"37+drivers="$fixture/drivers"38+bin="$fixture/bin"39+factory_fixture="$fixture/factories.tsv"40+install -d -m 0700 \41+ "$sysfs_root" "$device_root" "$drivers/amdgpu" "$drivers/i915" "$bin"42+43+add_render_node() {44+ local name="$1"45+ local driver="$2"46+47+ install -d -m 0700 "$sysfs_root/$name/device"48+ ln -s "$drivers/$driver" "$sysfs_root/$name/device/driver"49+ install -m 0600 /dev/null "$device_root/$name"50+}51+52+reset_render_nodes() {53+ rm -rf -- "$sysfs_root"54+ rm -rf -- "$device_root"55+ install -d -m 0700 "$sysfs_root" "$device_root"56+}57+58+cat >"$bin/gst-inspect-1.0" <<'EOF'59+#!/usr/bin/env bash60+set -euo pipefail61+62+fixture="${SIGIL_TEST_FACTORY_FIXTURE:?}"63+if [[ "${SIGIL_TEST_GST_MODE:-}" == oversize-factory && $# -gt 0 ]]; then64+ awk 'BEGIN { for (i = 0; i < 1048578; i++) printf "x" }'65+ exit 066+fi67+if [[ $# -eq 0 ]]; then68+ awk -F '\t' '{ printf "va: %s: fixture encoder\n", $1 }' "$fixture"69+ exit 070+fi71+72+record="$(awk -F '\t' -v factory="$1" '$1 == factory { print; exit }' "$fixture")"73+[[ -n "$record" ]] || exit 174+IFS=$'\t' read -r factory device modes completeness <<<"$record"75+printf '%s\n' \76+ ' device-path : DRM render device' \77+ ' flags: readable' \78+ " String. Default: \"$device\"" \79+ ' rate-control : Rate control mode' \80+ ' flags: readable, writable'81+case ",$modes," in82+ *,cqp,*) printf '%s\n' ' (1): cqp' ;;83+esac84+case ",$modes," in85+ *,cbr,*) printf '%s\n' ' (2): cbr' ;;86+esac87+for property in aud b-frames key-int-max ref-frames target-usage bitrate qpi qpp; do88+ [[ "$completeness" == "missing-$property" ]] && continue89+ printf ' %-20s: fixture property\n' "$property"90+ printf '%s\n' ' flags: readable, writable'91+done92+EOF93+chmod 0755 "$bin/gst-inspect-1.0"94+cat >"$bin/timeout" <<'EOF'95+#!/usr/bin/env bash96+set -euo pipefail97+98+[[ "${SIGIL_TEST_TIMEOUT:-}" != true ]] || exit 12499+[[ "${1:-}" == --signal=TERM ]]100+shift101+[[ "${1:-}" == --kill-after=2s ]]102+shift103+[[ "${1:-}" == 5s ]]104+shift105+exec "$@"106+EOF107+chmod 0755 "$bin/timeout"108+export SIGIL_TEST_FACTORY_FIXTURE="$factory_fixture"109+110+select_fixture() {111+ select_amd_gstva_h264_encoder \112+ "$bin/gst-inspect-1.0" "$sysfs_root" "$device_root" false \113+ "${1:-}" "${2:-}" "$bin/timeout" "$fixture"114+}115+116+reset_render_nodes117+add_render_node renderD128 i915118+add_render_node renderD129 amdgpu119+printf '%s\t%s\t%s\t%s\n' \120+ vah264enc "$device_root/renderD128" cqp,cbr complete \121+ varenderD129h264lpenc "$device_root/renderD129" cqp complete \122+ varenderD129h264enc "$device_root/renderD129" cqp,cbr complete \123+ >"$factory_fixture"124+select_fixture || fail 'a matching non-primary AMD encoder was not selected'125+[[ "$render_node" == "$device_root/renderD129" ]] || \126+ fail 'the Intel renderD128 node was selected instead of the AMD node'127+[[ "$va_encoder" == varenderD129h264enc ]] || \128+ fail 'the exact CBR+CQP per-device factory was not selected'129+130+reset_render_nodes131+add_render_node renderD130 amdgpu132+printf '%s\t%s\t%s\t%s\n' \133+ vah264enc "$device_root/renderD130" cqp,cbr complete \134+ >"$factory_fixture"135+select_fixture || fail 'a generic factory backed by renderD130 was not selected'136+[[ "$render_node" == "$device_root/renderD130" && "$va_encoder" == vah264enc ]] || \137+ fail 'selection retained a renderD128 or per-device factory assumption'138+139+reset_render_nodes140+add_render_node renderD128 amdgpu141+add_render_node renderD129 amdgpu142+printf '%s\t%s\t%s\t%s\n' \143+ vah264enc "$device_root/renderD128" cqp,cbr complete \144+ varenderD129h264enc "$device_root/renderD129" cqp,cbr complete \145+ >"$factory_fixture"146+selection_status=0147+select_fixture || selection_status=$?148+[[ "$selection_status" -eq 2 ]] || fail 'two viable AMD pairs were not ambiguous'149+[[ -z "$render_node" && -z "$va_encoder" ]] || \150+ fail 'ambiguous discovery retained a silent first selection'151+grep -Fxq "$device_root/renderD128 vah264enc" <<<"$va_candidates" || \152+ fail 'the first ambiguous pair was not reported'153+grep -Fxq "$device_root/renderD129 varenderD129h264enc" <<<"$va_candidates" || \154+ fail 'the second ambiguous pair was not reported'155+156+select_fixture "$device_root/renderD129" varenderD129h264enc || \157+ fail 'a valid explicit pair was rejected'158+[[ "$render_node" == "$device_root/renderD129" \159+ && "$va_encoder" == varenderD129h264enc ]] || \160+ fail 'the explicit pair did not win ambiguous discovery'161+if select_fixture "$device_root/renderD129" vah264enc; then162+ fail 'a mismatched explicit node/factory pair was accepted'163+fi164+165+reset_render_nodes166+add_render_node renderD130 amdgpu167+printf '%s\t%s\t%s\t%s\n' \168+ vah264enc "$device_root/renderD129" cqp,cbr complete \169+ >"$factory_fixture"170+if select_fixture; then171+ fail 'a factory whose device-path mismatched the AMD node was accepted'172+fi173+174+for modes in cqp cbr; do175+ printf '%s\t%s\t%s\t%s\n' \176+ vah264enc "$device_root/renderD130" "$modes" complete \177+ >"$factory_fixture"178+ if select_fixture; then179+ fail "a factory advertising only $modes was accepted"180+ fi181+done182+printf '%s\t%s\t%s\t%s\n' \183+ vah264enc "$device_root/renderD130" cqp,cbr missing-bitrate \184+ >"$factory_fixture"185+if select_fixture; then186+ fail 'a factory missing a required writable property was accepted'187+fi188+189+SIGIL_TEST_GST_MODE=oversize-factory190+export SIGIL_TEST_GST_MODE191+if select_fixture; then192+ fail 'oversized factory inspection output was accepted'193+fi194+unset SIGIL_TEST_GST_MODE195+196+SIGIL_TEST_TIMEOUT=true197+export SIGIL_TEST_TIMEOUT198+if select_fixture; then199+ fail 'a timed-out GStreamer registry inspection was accepted'200+fi201+unset SIGIL_TEST_TIMEOUT202+203+printf '%s\t%s\t%s\t%s\n' \204+ x264enc "$device_root/renderD130" cqp,cbr complete \205+ vah264enc-malformed "$device_root/renderD130" cqp,cbr complete \206+ >"$factory_fixture"207+if select_fixture; then208+ fail 'malformed or software factory names were accepted'209+fi210+211+printf '%s\t%s\t%s\t%s\n' \212+ vah264enc "$device_root/renderD130" cqp,cbr complete \213+ vah264enc "$device_root/renderD130" cqp,cbr complete \214+ >"$factory_fixture"215+select_fixture || fail 'duplicate listing of one factory became ambiguous'216+[[ "$va_encoder" == vah264enc ]] || fail 'duplicate factory selection was unstable'217+218+rm -f -- "$device_root/renderD130"219+ln -s "$temp_root/missing-render-node" "$device_root/renderD130"220+if select_fixture; then221+ fail 'a symlink render node was accepted'222+fi223+rm -f -- "$device_root/renderD130"224+install -m 0600 /dev/null "$device_root/renderD130"225+if select_amd_gstva_h264_encoder \226+ "$bin/gst-inspect-1.0" "$sysfs_root" "$device_root" true '' '' \227+ "$bin/timeout" "$fixture"228+then229+ fail 'a regular file was accepted as a production DRM character device'230+fi231+[[ -z "$render_node" && -z "$va_encoder" ]] || \232+ fail 'failed discovery retained stale selection state'233+234+encoder_config_line="vaapi_encoder = \"\$va_encoder\""235+render_node_config_line="vaapi_render_node = \"\$render_node\""236+[[ "$(grep -Fc "$encoder_config_line" "$runner")" -eq 3 ]] || \237+ fail 'not every generated UAT config uses the selected factory'238+[[ "$(grep -Fc "$render_node_config_line" "$runner")" -eq 3 ]] || \239+ fail 'not every generated UAT config uses the selected render node'240+if grep -Eq 'render_node=/dev/dri/renderD128|va_encoder=vah264enc' "$runner"; then241+ fail 'the hardware runner still pins the original render node or factory'242+fi243+244+fake_commit=0123456789abcdef0123456789abcdef01234567245+parser_error="$temp_root/parser.error"246+parser_status=0247+SIGIL_HARDWARE_UAT_SOURCE_ONLY=0 bash "$runner" /invalid "$fake_commit" 1 \248+ --render-node /dev/dri/renderD129 > /dev/null 2>"$parser_error" \249+ || parser_status=$?250+[[ "$parser_status" -eq 64 ]] || fail 'a partial override did not fail as usage error'251+grep -Fxq -- '--render-node and --va-encoder must be provided together' \252+ "$parser_error" || fail 'a partial override did not explain the paired requirement'253+254+parser_status=0255+SIGIL_HARDWARE_UAT_SOURCE_ONLY=0 bash "$runner" /invalid "$fake_commit" 1 \256+ --unknown value > /dev/null 2>"$parser_error" || parser_status=$?257+[[ "$parser_status" -eq 64 ]] || fail 'an unknown override did not fail as usage error'258+grep -Fq 'usage:' "$parser_error" || fail 'an unknown override did not print usage'259+260+parser_status=0261+SIGIL_HARDWARE_UAT_SOURCE_ONLY=0 bash "$runner" /invalid "$fake_commit" 1 \262+ --render-node /dev/dri/renderD128 --render-node /dev/dri/renderD129 \263+ --va-encoder vah264enc > /dev/null 2>"$parser_error" || parser_status=$?264+[[ "$parser_status" -eq 64 ]] || fail 'a duplicate override was accepted'265+266+parser_status=0267+SIGIL_HARDWARE_UAT_SOURCE_ONLY=0 bash "$runner" /invalid "$fake_commit" 1 \268+ --render-node '' --va-encoder vah264enc \269+ > /dev/null 2>"$parser_error" || parser_status=$?270+[[ "$parser_status" -eq 64 ]] || fail 'an empty override value was accepted'271+272+printf 'bazzite_hardware_uat_selection_tests=ok\n'