Skip to content

Commit 2ea4612

Browse files
committed
Add GLx/Vulkan CI and audio device recovery
Add GitHub workflows for GLx and Vulkan verification (logic/gate planning and optional self-hosted runtime sweeps). Introduce audio device recovery and streaming improvements: new AudioDeviceRecovery.h, device polling/recovery integration in AudioSystem, OpenAL helpers (config hints, RecoverDevice), cubic-resampling/interpolated stream conversion, and stream output rate selection. Extend OpenAL device capabilities (sample format support, PCM format mapping, device connection refresh) and expose related CLI hooks. Add CMake flags/tests for deterministic GLx logic and audio tests, new test binaries, runtime sweep scripts/tests, renderer docs and various header/logic files to support the changes.
1 parent 3235f61 commit 2ea4612

64 files changed

Lines changed: 14607 additions & 2012 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/FUNDING.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
github: themuffinator
Lines changed: 299 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,299 @@
1+
name: glx-verification
2+
3+
permissions:
4+
contents: read
5+
6+
env:
7+
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
8+
9+
on:
10+
pull_request:
11+
paths:
12+
- '.github/workflows/glx-verification.yml'
13+
- 'CMakeLists.txt'
14+
- 'code/renderer/**'
15+
- 'code/renderercommon/**'
16+
- 'code/rendererglx/**'
17+
- 'docs/fnquake3/GLX_*.md'
18+
- 'scripts/glx_runtime_sweep.py'
19+
- 'tests/glx/**'
20+
push:
21+
branches:
22+
- main
23+
paths:
24+
- '.github/workflows/glx-verification.yml'
25+
- 'CMakeLists.txt'
26+
- 'code/renderer/**'
27+
- 'code/renderercommon/**'
28+
- 'code/rendererglx/**'
29+
- 'docs/fnquake3/GLX_*.md'
30+
- 'scripts/glx_runtime_sweep.py'
31+
- 'tests/glx/**'
32+
workflow_dispatch:
33+
inputs:
34+
run_runtime_sweep:
35+
description: Run the selected GLx gate on a self-hosted GPU runner with retail assets.
36+
required: true
37+
default: false
38+
type: boolean
39+
runtime_runner_labels:
40+
description: JSON array of self-hosted runner labels.
41+
required: true
42+
default: '["self-hosted","glx"]'
43+
type: string
44+
runtime_gate:
45+
description: GLx RC gate to execute on the runtime runner.
46+
required: true
47+
default: rc-smoke
48+
type: choice
49+
options:
50+
- rc-smoke
51+
- rc-parity
52+
- rc-stress
53+
runtime_exe:
54+
description: Path to the built FnQuake3 client executable on the runtime runner.
55+
required: false
56+
default: ''
57+
type: string
58+
runtime_basepath:
59+
description: Path containing retail baseq3 assets on the runtime runner.
60+
required: false
61+
default: ''
62+
type: string
63+
screenshot_baseline_dir:
64+
description: Optional approved screenshot baseline directory on the runtime runner.
65+
required: false
66+
default: ''
67+
type: string
68+
approve_screenshot_baselines:
69+
description: Write current captures into screenshot_baseline_dir instead of comparing.
70+
required: true
71+
default: false
72+
type: boolean
73+
screenshot_max_rms:
74+
description: Maximum allowed RGB RMS screenshot difference.
75+
required: true
76+
default: '2.0'
77+
type: string
78+
screenshot_max_pixel_ratio:
79+
description: Maximum allowed ratio of changed screenshot pixels.
80+
required: true
81+
default: '0.005'
82+
type: string
83+
performance_budget:
84+
description: Optional GLx performance budget JSON on the runtime runner.
85+
required: false
86+
default: ''
87+
type: string
88+
performance_baseline:
89+
description: Optional approved GLx performance baseline JSON on the runtime runner.
90+
required: false
91+
default: ''
92+
type: string
93+
approve_performance_baseline:
94+
description: Write current aggregate performance samples into performance_baseline instead of comparing.
95+
required: true
96+
default: false
97+
type: boolean
98+
performance_max_growth_ratio:
99+
description: Maximum allowed counter growth versus the performance baseline.
100+
required: true
101+
default: '0.20'
102+
type: string
103+
104+
jobs:
105+
glx-logic-tests:
106+
name: GLx logic tests
107+
runs-on: ubuntu-24.04
108+
steps:
109+
- uses: actions/checkout@v6
110+
111+
- name: Install build tools
112+
run: |
113+
sudo apt-get -qq update
114+
sudo apt-get install -y cmake ninja-build g++ libx11-dev libxext-dev libxxf86dga-dev libxrandr-dev libxxf86vm-dev mesa-common-dev
115+
116+
- name: Configure focused test build
117+
run: |
118+
cmake -S . -B .tmp/cmake-glx-verification -G Ninja \
119+
-DUSE_SDL=OFF \
120+
-DUSE_CURL=OFF \
121+
-DUSE_VULKAN=OFF \
122+
-DUSE_GLX=ON \
123+
-DFNQ3_AUDIO_LOOPBACK_TESTS=OFF \
124+
-DFNQ3_GLX_LOGIC_TESTS=ON
125+
126+
- name: Build GLx logic tests
127+
run: cmake --build .tmp/cmake-glx-verification --target fnq3_glx_logic_tests
128+
129+
- name: Run GLx logic tests
130+
run: ctest --test-dir .tmp/cmake-glx-verification -R fnq3_glx_logic --output-on-failure
131+
132+
glx-gate-plans:
133+
name: GLx RC gate plans
134+
runs-on: ubuntu-24.04
135+
steps:
136+
- uses: actions/checkout@v6
137+
138+
- name: List GLx RC gates
139+
run: python scripts/glx_runtime_sweep.py --list-gates
140+
141+
- name: Run GLx sweep Python tests
142+
run: python tests/glx/glx_runtime_sweep_tests.py
143+
144+
- name: Generate dry-run gate artifacts
145+
run: |
146+
mkdir -p .tmp/glx-gate-plans
147+
for gate in rc-smoke rc-parity rc-stress; do
148+
python scripts/glx_runtime_sweep.py \
149+
--gate "$gate" \
150+
--dry-run \
151+
--exe ".tmp/glx-gate-plans/fnquake3.glx" \
152+
--basepath ".tmp/glx-gate-plans/basepath" \
153+
--output-dir ".tmp/glx-gate-plans" \
154+
--summary-markdown ".tmp/glx-gate-plans/${gate}.md"
155+
cat ".tmp/glx-gate-plans/${gate}.md" >> "$GITHUB_STEP_SUMMARY"
156+
done
157+
158+
- name: Upload dry-run gate artifacts
159+
uses: actions/upload-artifact@v7
160+
with:
161+
name: glx-gate-plans
162+
path: .tmp/glx-gate-plans
163+
if-no-files-found: error
164+
retention-days: 14
165+
166+
glx-runtime-sweep:
167+
name: GLx runtime sweep
168+
if: github.event_name == 'workflow_dispatch' && inputs.run_runtime_sweep
169+
runs-on: ${{ fromJSON(inputs.runtime_runner_labels || '["self-hosted","glx"]') }}
170+
steps:
171+
- uses: actions/checkout@v6
172+
173+
- name: Validate runtime inputs
174+
shell: python
175+
env:
176+
FNQ3_GLX_RUNTIME_EXE: ${{ inputs.runtime_exe }}
177+
FNQ3_GLX_RUNTIME_BASEPATH: ${{ inputs.runtime_basepath }}
178+
FNQ3_GLX_SCREENSHOT_BASELINE_DIR: ${{ inputs.screenshot_baseline_dir }}
179+
FNQ3_GLX_APPROVE_SCREENSHOT_BASELINES: ${{ inputs.approve_screenshot_baselines }}
180+
FNQ3_GLX_PERFORMANCE_BASELINE: ${{ inputs.performance_baseline }}
181+
FNQ3_GLX_APPROVE_PERFORMANCE_BASELINE: ${{ inputs.approve_performance_baseline }}
182+
run: |
183+
import os
184+
import sys
185+
186+
missing = [
187+
name
188+
for name in ("FNQ3_GLX_RUNTIME_EXE", "FNQ3_GLX_RUNTIME_BASEPATH")
189+
if not os.environ.get(name)
190+
]
191+
if missing:
192+
for name in missing:
193+
print(f"{name} is required when run_runtime_sweep is true.", file=sys.stderr)
194+
sys.exit(2)
195+
if (
196+
os.environ.get("FNQ3_GLX_APPROVE_SCREENSHOT_BASELINES") == "true" and
197+
not os.environ.get("FNQ3_GLX_SCREENSHOT_BASELINE_DIR")
198+
):
199+
print(
200+
"screenshot_baseline_dir is required when approving screenshot baselines.",
201+
file=sys.stderr,
202+
)
203+
sys.exit(2)
204+
if (
205+
os.environ.get("FNQ3_GLX_APPROVE_PERFORMANCE_BASELINE") == "true" and
206+
not os.environ.get("FNQ3_GLX_PERFORMANCE_BASELINE")
207+
):
208+
print(
209+
"performance_baseline is required when approving performance baselines.",
210+
file=sys.stderr,
211+
)
212+
sys.exit(2)
213+
214+
- name: Run GLx runtime gate
215+
shell: python
216+
env:
217+
FNQ3_GLX_RUNTIME_GATE: ${{ inputs.runtime_gate }}
218+
FNQ3_GLX_RUNTIME_EXE: ${{ inputs.runtime_exe }}
219+
FNQ3_GLX_RUNTIME_BASEPATH: ${{ inputs.runtime_basepath }}
220+
FNQ3_GLX_SCREENSHOT_BASELINE_DIR: ${{ inputs.screenshot_baseline_dir }}
221+
FNQ3_GLX_APPROVE_SCREENSHOT_BASELINES: ${{ inputs.approve_screenshot_baselines }}
222+
FNQ3_GLX_SCREENSHOT_MAX_RMS: ${{ inputs.screenshot_max_rms }}
223+
FNQ3_GLX_SCREENSHOT_MAX_PIXEL_RATIO: ${{ inputs.screenshot_max_pixel_ratio }}
224+
FNQ3_GLX_PERFORMANCE_BUDGET: ${{ inputs.performance_budget }}
225+
FNQ3_GLX_PERFORMANCE_BASELINE: ${{ inputs.performance_baseline }}
226+
FNQ3_GLX_APPROVE_PERFORMANCE_BASELINE: ${{ inputs.approve_performance_baseline }}
227+
FNQ3_GLX_PERFORMANCE_MAX_GROWTH_RATIO: ${{ inputs.performance_max_growth_ratio }}
228+
run: |
229+
import os
230+
import subprocess
231+
import sys
232+
from pathlib import Path
233+
234+
gate = os.environ["FNQ3_GLX_RUNTIME_GATE"]
235+
output_dir = Path(".tmp") / "glx-runtime-sweeps"
236+
summary_path = output_dir / f"{gate}.md"
237+
command = [
238+
sys.executable,
239+
"scripts/glx_runtime_sweep.py",
240+
"--gate",
241+
gate,
242+
"--exe",
243+
os.environ["FNQ3_GLX_RUNTIME_EXE"],
244+
"--basepath",
245+
os.environ["FNQ3_GLX_RUNTIME_BASEPATH"],
246+
"--output-dir",
247+
str(output_dir),
248+
"--summary-markdown",
249+
str(summary_path),
250+
]
251+
baseline_dir = os.environ.get("FNQ3_GLX_SCREENSHOT_BASELINE_DIR", "")
252+
if baseline_dir:
253+
command.extend(
254+
[
255+
"--screenshot-baseline-dir",
256+
baseline_dir,
257+
"--screenshot-diff-dir",
258+
str(output_dir / "diffs"),
259+
"--screenshot-max-rms",
260+
os.environ["FNQ3_GLX_SCREENSHOT_MAX_RMS"],
261+
"--screenshot-max-pixel-ratio",
262+
os.environ["FNQ3_GLX_SCREENSHOT_MAX_PIXEL_RATIO"],
263+
]
264+
)
265+
if os.environ.get("FNQ3_GLX_APPROVE_SCREENSHOT_BASELINES") == "true":
266+
command.append("--approve-screenshot-baselines")
267+
268+
performance_budget = os.environ.get("FNQ3_GLX_PERFORMANCE_BUDGET", "")
269+
if performance_budget:
270+
command.extend(["--performance-budget", performance_budget])
271+
272+
performance_baseline = os.environ.get("FNQ3_GLX_PERFORMANCE_BASELINE", "")
273+
if performance_baseline:
274+
command.extend(
275+
[
276+
"--performance-baseline",
277+
performance_baseline,
278+
"--performance-max-growth-ratio",
279+
os.environ["FNQ3_GLX_PERFORMANCE_MAX_GROWTH_RATIO"],
280+
]
281+
)
282+
if os.environ.get("FNQ3_GLX_APPROVE_PERFORMANCE_BASELINE") == "true":
283+
command.append("--approve-performance-baseline")
284+
285+
subprocess.check_call(command)
286+
287+
github_summary = os.environ.get("GITHUB_STEP_SUMMARY")
288+
if github_summary:
289+
with open(github_summary, "a", encoding="utf-8") as handle:
290+
handle.write(summary_path.read_text(encoding="utf-8"))
291+
292+
- name: Upload runtime gate artifacts
293+
if: always()
294+
uses: actions/upload-artifact@v7
295+
with:
296+
name: glx-runtime-sweep-${{ inputs.runtime_gate }}
297+
path: .tmp/glx-runtime-sweeps
298+
if-no-files-found: error
299+
retention-days: 30

0 commit comments

Comments
 (0)