mirror of
https://github.com/libretro/virtualjaguar-libretro.git
synced 2026-11-07 12:47:58 -10:00
ci(gl-qualification): capture waits for core frames; unbuffered shaders_gl; macOS 640x480 + capture diagnostics
Run 41727418224 (3eef3305) read per leg:
- Windows x64: the preset loop passed under the tracked exit hang (494 built, 501 launches killed 5 s after
their screenshot, #1167) but the shaders_gl allowance failed: the test's stdout verdict lines were lost when
the hung process was killed (a pipe is fully buffered; only the stderr [shader] lines survived).
vj_shader_tests writes stdout unbuffered from main() on.
- Windows arm64: 493 built, CuNNy-8x32-nvl-2x-rgb timed out at 150 s on the 128-bit llvmpipe. The Windows
per-launch timeout is 240 s like the other legs.
- macOS: shaders_gl passes at 384x288 (10 min). In the seeded 480x360 window every capture, --shader off
included, came back a uniform white picture at 1.9 s a launch; not the game (yarc is black at core frame
1-2 and shows its picture from frame 10 on), not the theme. The leg seeds 640x480 (fits the hosted display,
keeps crt-royale above 341 px) and a "Capture diagnostics" step launches `off` once per window
configuration (default, 480x360, +frame 120, +emu frames, no viewports, no vsync, 480x362, 640x480,
960x760) and prints each picture's statistics to the step summary, so the artifact names the variable.
App: --screenshot-emu-frames M (dev): the capture also waits until the core has shown M frames, with a line
and a capture anyway 600 UI frames later if it never does; gl_qualify.py --emu-frames passes it, every leg
uses 60. VideoOut::emuFrames() exposes the counter.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
This commit is contained in:
1 parent
3eef3305e2
commit
6afaae1aa8
9 files changed
+102
-22
No files matched your search
@@ -84,9 +84,10 @@ env:
|
||||
REFRESH_GOLDENS: ${{ inputs.refresh_goldens && 'true' || 'false' }}
|
||||
# The picture the chains filter: the committed public ROM the smoke tests use.
|
||||
CONTENT_ROM: test/roms/yarc.j64
|
||||
# UI frames before the capture. The emulation thread runs the core meanwhile, so the game
|
||||
# has drawn long before frame 20 even on a slow software renderer.
|
||||
# UI frames before the capture, and core frames the capture also waits for (yarc shows its picture from
|
||||
# core frame 10 on), so the screenshot is the game's picture however fast or crowded the host is.
|
||||
SHOT_FRAME: '20'
|
||||
SHOT_EMU_FRAMES: '60'
|
||||
QUALIFY: app/test/gl-qualification/gl_qualify.py
|
||||
LISTS: app/test/gl-qualification
|
||||
|
||||
@@ -152,7 +153,7 @@ jobs:
|
||||
export LIBGL_ALWAYS_SOFTWARE=1 SDL_VIDEO_DRIVER=x11
|
||||
xvfb-run -a -s '-screen 0 1600x1200x24 +extension GLX' \
|
||||
python3 -I "$QUALIFY" --os linux --exe stage/bin/virtualjaguar --shaders stage/bin/shaders \
|
||||
--out glq --content "$CONTENT_ROM" --frame "$SHOT_FRAME" --jobs 2 --timeout 240 \
|
||||
--out glq --content "$CONTENT_ROM" --frame "$SHOT_FRAME" --emu-frames "$SHOT_EMU_FRAMES" --jobs 2 --timeout 240 \
|
||||
--expect-renderer 'llvmpipe' \
|
||||
--refused-allowlist "$LISTS/refused-linux.txt" --identical-ok "$LISTS/identical-to-plain.txt" --known-broken "$LISTS/known-broken.txt" \
|
||||
--goldens app/test/golden/gl/linux --preset-set "$PRESET_SET" ${refresh[@]+"${refresh[@]}"}
|
||||
@@ -415,7 +416,7 @@ jobs:
|
||||
fi
|
||||
hang=(); [ -z "${{ steps.known.outputs.exit_hang }}" ] || hang=(--known-exit-hang "${{ steps.known.outputs.exit_hang }}")
|
||||
python -I "$QUALIFY" --os windows --exe stage/bin/virtualjaguar.exe --shaders stage/bin/shaders \
|
||||
--out glq --content "$CONTENT_ROM" --frame "$SHOT_FRAME" --jobs 1 --timeout 150 ${hang[@]+"${hang[@]}"} \
|
||||
--out glq --content "$CONTENT_ROM" --frame "$SHOT_FRAME" --emu-frames "$SHOT_EMU_FRAMES" --jobs 1 --timeout 240 ${hang[@]+"${hang[@]}"} \
|
||||
--expect-renderer 'llvmpipe|D3D12' ${expect_log[@]+"${expect_log[@]}"} \
|
||||
--refused-allowlist "$LISTS/refused-windows.txt" --identical-ok "$LISTS/identical-to-plain.txt" --known-broken "$LISTS/known-broken.txt" \
|
||||
--goldens app/test/golden/gl/windows --preset-set "$PRESET_SET" ${refresh[@]+"${refresh[@]}"}
|
||||
@@ -436,8 +437,9 @@ jobs:
|
||||
timeout 600 build/vj_shader_tests.exe gl 2>&1 | tee glq/shaders_gl.txt
|
||||
code=${PIPESTATUS[0]}
|
||||
set -e
|
||||
# The tracked exit hang: the test's verdict line comes after SDL_Quit, which never
|
||||
# returns there, so the evidence is "nothing failed or skipped and the last check ran".
|
||||
# The tracked exit hang: the test's final verdict line comes after SDL_Quit, which never
|
||||
# returns there, so the evidence is "nothing failed or skipped and the last check ran"
|
||||
# (the test writes stdout unbuffered, so a killed run still has every line before it).
|
||||
if [ "$code" -eq 124 ] && [ -n "${{ steps.known.outputs.exit_hang }}" ] \
|
||||
&& ! grep -q 'shader_tests: FAIL\|shader_tests gl: SKIP' glq/shaders_gl.txt \
|
||||
&& grep -q '^broken shader -> chain build failed' glq/shaders_gl.txt; then
|
||||
@@ -512,15 +514,64 @@ jobs:
|
||||
perl -e 'alarm shift; exec @ARGV' 120 build/vj_gl_context_integration 2>&1 | tee glq/gl-context-integration.txt
|
||||
test "${PIPESTATUS[0]}" -eq 0
|
||||
|
||||
# Run 41727418224: in a seeded 480x360 window every capture (--shader off included) came back a uniform
|
||||
# white, 1.9 s a launch, while 960x642 windows (run 3) and the other legs' 960x760 windows show the game.
|
||||
# The same `off` capture under several window configurations, side by side, so the artifact says which
|
||||
# of the size, the seeded config.ini, ImGui's extra OS windows or vsync is the variable.
|
||||
- name: Capture diagnostics (one `off` launch per window configuration)
|
||||
run: |
|
||||
set -u
|
||||
D="$PWD/glq/capture-diag"; mkdir -p "$D"
|
||||
system_profiler SPDisplaysDataType 2>/dev/null | grep -E 'Resolution|UI Looks like' | sed 's/^ *//' | tee "$D/display.txt" || true
|
||||
run_one() { # name, extra app args..., with CFG holding the config.ini text ("" = none)
|
||||
local name=$1; shift
|
||||
local data="$D/$name"; mkdir -p "$data"
|
||||
[ -n "$CFG" ] && printf '%s\n' "$CFG" > "$data/config.ini"
|
||||
local t0=$SECONDS
|
||||
perl -e 'alarm shift; exec @ARGV' 90 stage/bin/virtualjaguar --data-dir "$data" --shader off \
|
||||
--screenshot "$D/$name.png" --screenshot-frame "$SHOT_FRAME" --low-dpi "$@" "$CONTENT_ROM" \
|
||||
> "$D/$name.log" 2>&1
|
||||
local rc=$? dt=$((SECONDS - t0))
|
||||
python3 -I - "$D/$name.png" "$name" "$rc" "$dt" <<'PY' | tee -a "$D/summary.txt"
|
||||
import sys, os
|
||||
sys.path.insert(0, os.path.join(os.environ["GITHUB_WORKSPACE"], "app", "test", "gl-qualification"))
|
||||
png, name, rc, dt = sys.argv[1:5]
|
||||
try:
|
||||
import gl_qualify as g
|
||||
p = g.Png.read(png)
|
||||
seen = {}
|
||||
for row in p.rows:
|
||||
for x in range(p.width):
|
||||
c = tuple(row[x * p.channels:x * p.channels + 3]); seen[c] = seen.get(c, 0) + 1
|
||||
top = sorted(seen.items(), key=lambda kv: -kv[1])[:3]
|
||||
lit, mean = p.rgb_stats()
|
||||
print("%-22s exit %-3s %3ss %dx%d lit %.3f mean %6.1f colours %d top %s" % (name, rc, dt, p.width, p.height, lit, mean, len(seen), top))
|
||||
except Exception as e:
|
||||
print("%-22s exit %-3s %3ss no picture (%s)" % (name, rc, dt, e))
|
||||
PY
|
||||
}
|
||||
CFG="" run_one default-960x760
|
||||
CFG=$'window.w=480\nwindow.h=360' run_one seeded-480x360
|
||||
CFG=$'window.w=480\nwindow.h=360' run_one seeded-480x360-f120 --screenshot-frame 120
|
||||
CFG=$'window.w=480\nwindow.h=360' run_one seeded-480x360-emu60 --screenshot-emu-frames 60
|
||||
CFG=$'window.w=480\nwindow.h=360\nui.viewports=0' run_one seeded-480x360-novp
|
||||
CFG=$'window.w=480\nwindow.h=360\nvideo.vsync=0' run_one seeded-480x360-novsync
|
||||
CFG=$'window.w=480\nwindow.h=362' run_one seeded-480x362
|
||||
CFG=$'window.w=640\nwindow.h=480' run_one seeded-640x480
|
||||
CFG=$'window.w=960\nwindow.h=760' run_one seeded-960x760
|
||||
echo; echo "## macOS capture diagnostics" >> "$GITHUB_STEP_SUMMARY"
|
||||
{ echo '```'; cat "$D/display.txt" "$D/summary.txt"; echo '```'; } >> "$GITHUB_STEP_SUMMARY"
|
||||
rm -rf "${D:?}"/*/ # the data folders (lock files, saves) are noise in the artifact
|
||||
|
||||
- name: Render every bundled preset
|
||||
run: |
|
||||
set -eu
|
||||
# (${arr[@]+"${arr[@]}"}: an empty array under set -u is an error on macOS's bash 3.2)
|
||||
refresh=(); [ "$REFRESH_GOLDENS" = true ] && refresh=(--refresh-goldens)
|
||||
# Apple's software renderer: ~75 s a launch at 960x760 (run 41571727219 managed 205 of 500 in
|
||||
# 150 min at 2 jobs), so this leg runs 3 launches at once in a 480x360 window.
|
||||
# The hosted display is too small for the default 960x760 window (it came back 960x642 and ~45 s a
|
||||
# launch; run 41571727219 managed 205 of 500 in 150 min at 2 jobs): 640x480 (small-window.ini), 3 at once.
|
||||
python3 -I "$QUALIFY" --os macos --exe stage/bin/virtualjaguar --shaders stage/bin/shaders \
|
||||
--out glq --content "$CONTENT_ROM" --frame "$SHOT_FRAME" --jobs 3 --timeout 240 --low-dpi \
|
||||
--out glq --content "$CONTENT_ROM" --frame "$SHOT_FRAME" --emu-frames "$SHOT_EMU_FRAMES" --jobs 3 --timeout 240 --low-dpi \
|
||||
--seed-config "$LISTS/small-window.ini" --expect-renderer 'Apple' \
|
||||
--refused-allowlist "$LISTS/refused-macos.txt" --identical-ok "$LISTS/identical-to-plain.txt" --known-broken "$LISTS/known-broken.txt" \
|
||||
--goldens app/test/golden/gl/macos --preset-set "$PRESET_SET" ${refresh[@]+"${refresh[@]}"}
|
||||
|
||||
+13
-3
@@ -2808,9 +2808,19 @@ int App::runInteractive(const CliArgs& a) {
|
||||
if (perf.on && perf.gpu) Perf::finish();
|
||||
perf.lap(Perf::Ui);
|
||||
tvScriptFrameEnd(); // the script's `shot`: this frame's TV drawing is in the buffer, not yet swapped
|
||||
if (!a.screenshot.empty() && ++uiFrames == a.screenshotFrame) {
|
||||
saveWindowScreenshot(a.screenshot);
|
||||
quit = true;
|
||||
// --screenshot: after N UI frames and (--screenshot-emu-frames M) once the core has shown M frames, so the
|
||||
// capture is the game's picture whatever the host's speed. A core that never gets there (no content, a
|
||||
// halt) is captured 600 UI frames later anyway, with a line saying so.
|
||||
if (!a.screenshot.empty() && ++uiFrames >= a.screenshotFrame) {
|
||||
const bool emuReady = video.emuFrames() >= a.screenshotEmuFrames;
|
||||
const bool gaveUp = !emuReady && uiFrames >= a.screenshotFrame + 600;
|
||||
if (emuReady || gaveUp) {
|
||||
if (gaveUp)
|
||||
std::fprintf(stderr, "[shot] the core showed %llu frame(s) in %u UI frames, wanted %u: capturing anyway\n",
|
||||
(unsigned long long)video.emuFrames(), uiFrames, a.screenshotEmuFrames);
|
||||
saveWindowScreenshot(a.screenshot);
|
||||
quit = true;
|
||||
}
|
||||
}
|
||||
presentViewports();
|
||||
guardSwap(); // before the timestamps: the latch must not see it as swap time
|
||||
|
||||
@@ -74,6 +74,7 @@ struct CliArgs {
|
||||
bool verbose = false;
|
||||
std::string screenshot; // dev: save the window's pixels to this PNG after --screenshot-frame frames, then exit
|
||||
unsigned screenshotFrame = 120;
|
||||
unsigned screenshotEmuFrames = 0; // dev: ... and not before the core has shown this many frames (--screenshot-emu-frames; 0 = UI frames only)
|
||||
std::string showWindow; // dev: open a window at start: settings[-video|-input|-audio|-storage|-interface][:FILTER], states, about, cheats, library, login, achievements, keypad
|
||||
std::string theme; // --theme system|dark|light: the colour theme for this run (not saved); "" = the saved choice
|
||||
std::string shader; // --shader NAME|FILE|off: the shader preset for this run (not saved); "" = the setting (#1154)
|
||||
|
||||
+3
-1
@@ -52,7 +52,8 @@ static void usage(const char* argv0) {
|
||||
"dev: --perf-frames N [--perf-gpu] [--perf-pause] [--perf-halt] [--perf-no-reuse] [--perf-shader-out WxH] [--perf-input]\n"
|
||||
" print where a UI frame's time goes over N frames (and the shader chain's GPU cost), then exit\n"
|
||||
"dev: --dump-native-menu print and check the real macOS menu bar and Dock menu, then exit (77 = none on this OS)\n"
|
||||
"dev: --screenshot PNG [--screenshot-frame N] [--ui-scale X] [--low-dpi] [--pixel-density N]\n"
|
||||
"dev: --screenshot PNG [--screenshot-frame N] [--screenshot-emu-frames M] [--ui-scale X] [--low-dpi] [--pixel-density N]\n"
|
||||
" (the capture waits for N UI frames and, with M, for the core to have shown M frames: a picture, not the boot)\n"
|
||||
" [--font retro|system|mono[:SIZE]|custom:FILE] [--font-then SPEC] (this run only; --font-then switches live after 60 frames)\n"
|
||||
" [--show-window settings[-video|-input|-audio|-bios|-storage|-interface]|states|about|cheats|library|login|achievements|keypad|netplay|controls|theme-editor]\n"
|
||||
" [--show-window memory|bitmap|palette|scanner|cpu|objects|blitter|textures|scope|tools] (Tools windows; needs a game; tools = all)\n"
|
||||
@@ -274,6 +275,7 @@ int main(int argc, char** argv) {
|
||||
}
|
||||
else if (!std::strcmp(s, "--screenshot")) a.screenshot = need(s);
|
||||
else if (!std::strcmp(s, "--screenshot-frame")) a.screenshotFrame = (unsigned)std::strtoul(need(s), nullptr, 10);
|
||||
else if (!std::strcmp(s, "--screenshot-emu-frames")) a.screenshotEmuFrames = (unsigned)std::strtoul(need(s), nullptr, 10);
|
||||
else if (!std::strcmp(s, "--show-window")) a.showWindow = need(s);
|
||||
else if (!std::strcmp(s, "--ui-scale")) a.uiScale = (float)std::strtod(need(s), nullptr);
|
||||
else if (!std::strcmp(s, "--low-dpi")) a.lowDpi = true;
|
||||
|
||||
@@ -58,6 +58,7 @@ public:
|
||||
// Small helper textures for save-state thumbnails (RGBA8, tight).
|
||||
static unsigned createTextureRGBA(const void* pixels, int w, int h);
|
||||
static void destroyTexture(unsigned tex);
|
||||
uint64_t emuFrames() const { return emuFrames_; } // distinct core frames uploaded so far
|
||||
|
||||
private:
|
||||
unsigned tex_ = 0;
|
||||
|
||||
@@ -217,6 +217,8 @@ def launch(args, label, shader_arg, out_dir):
|
||||
log = logs / (safe_name(label) + ".log")
|
||||
cmd = [str(args.exe), "--data-dir", str(data), "--shader", shader_arg,
|
||||
"--screenshot", str(png), "--screenshot-frame", str(args.frame)]
|
||||
if args.emu_frames:
|
||||
cmd += ["--screenshot-emu-frames", str(args.emu_frames)]
|
||||
if args.low_dpi:
|
||||
cmd.append("--low-dpi")
|
||||
cmd += args.extra_arg
|
||||
@@ -372,6 +374,9 @@ def main():
|
||||
ap.add_argument("--os", required=True, choices=["linux", "windows", "macos"], help="leg name, for the summary and the golden layout")
|
||||
ap.add_argument("--content", type=pathlib.Path, default=None, help="a ROM to run (the picture the chain filters); default: no content")
|
||||
ap.add_argument("--frame", type=int, default=20, help="--screenshot-frame (UI frames before the capture)")
|
||||
ap.add_argument("--emu-frames", type=int, default=0,
|
||||
help="--screenshot-emu-frames: the capture also waits until the core has shown this many frames "
|
||||
"(the picture is the game's, however fast or crowded the host)")
|
||||
ap.add_argument("--timeout", type=int, default=180, help="seconds per launch")
|
||||
ap.add_argument("--jobs", type=int, default=1, help="launches at once (each has its own --data-dir)")
|
||||
ap.add_argument("--known-exit-hang", default="", help="this leg's app is known not to exit (cite the issue): a launch with a complete screenshot is killed after --hang-grace seconds and passes with a note")
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
# Seeded as config.ini into every launch's data folder by gl_qualify.py --seed-config on legs
|
||||
# whose software renderer makes 960x760 too slow (hosted macOS, Apple Software Renderer: ~75 s a
|
||||
# launch; 205 of 500 presets in the 150-minute budget). 480x360 keeps crt-royale above the 341 px
|
||||
# its mask passes need. Screenshots and goldens of that leg are this size.
|
||||
window.w=480
|
||||
window.h=360
|
||||
# Seeded as config.ini into every launch's data folder by gl_qualify.py --seed-config on legs whose
|
||||
# display or software renderer makes the default 960x760 window a bad fit (hosted macOS: the display is
|
||||
# too small for it, the window came back clamped to 960x642 and ~45 s a launch; run 41571727219 managed
|
||||
# 205 of 500 presets in 150 min). 640x480 fits that display, keeps crt-royale above the 341 px its mask
|
||||
# passes need, and is not the 4:3 480x360 of run 41727418224, whose every capture came back a uniform
|
||||
# white (the "Capture diagnostics" step of that leg tries the window sizes side by side). Screenshots
|
||||
# and goldens of that leg are this size.
|
||||
window.w=640
|
||||
window.h=480
|
||||
@@ -885,6 +885,9 @@ static int runProbe(const char* dir) {
|
||||
}
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
// Unbuffered: a run that is killed (the x64 bundled Mesa never returns from SDL_Quit, #1167) must still have
|
||||
// every verdict line in its log; the stderr [shader] lines already are.
|
||||
std::setvbuf(stdout, nullptr, _IONBF, 0);
|
||||
std::string mode = argc > 1 ? argv[1] : "unit";
|
||||
if (mode == "probe") return runProbe(argc > 2 ? argv[2] : nullptr);
|
||||
if (mode == "unit") return runUnit();
|
||||
|
||||
@@ -387,7 +387,9 @@ it is `-E shaders_gl`; the step is `continue-on-error` and a "shaders_gl verdict
|
||||
job, so the screenshots exist even when the test is red, and a test that has to be killed cannot
|
||||
wedge the window server before the loop). The loop:
|
||||
`app/test/gl-qualification/gl_qualify.py` launches `stage/bin/virtualjaguar --shader <preset>
|
||||
--screenshot <png> --screenshot-frame 20 test/roms/yarc.j64` for `--shader off` and every preset.
|
||||
--screenshot <png> --screenshot-frame 20 --screenshot-emu-frames 60 test/roms/yarc.j64` for `--shader off`
|
||||
and every preset (the capture waits for 20 UI frames *and* 60 core frames, so it is the game's picture
|
||||
however fast or crowded the host is).
|
||||
Per (leg, preset) it asserts: exit 0 before the timeout; the `[gl] version=...; renderer=` line
|
||||
matches the leg (`llvmpipe` / `llvmpipe|D3D12` after `automatically retrying with bundled Mesa
|
||||
OpenGL` / `Apple`); the PNG decodes and is not black; a `[shader] <preset> built` line
|
||||
@@ -397,8 +399,10 @@ summary says how many were not run), so a leg with no working runtime fails in m
|
||||
`[shader] ... refused` line (macOS OpenGL 4.1 has no
|
||||
`GL_ARB_arrays_of_arrays`), or a `... failed` line for a preset the leg's GL cannot compile
|
||||
(packHalf2x16 on macOS), passes only for presets in `refused-<os>.txt`, so that set can shrink but
|
||||
never grow. The macOS leg runs in a 480x360 window (`small-window.ini`, `--seed-config`): Apple's
|
||||
software renderer takes ~75 s a launch at 960x760. Goldens are a second pass: `app/test/golden/gl/<os>/<preset>.png` is
|
||||
never grow. The macOS leg runs in a 640x480 window (`small-window.ini`, `--seed-config`): the hosted
|
||||
display is too small for 960x760 (the window came back 960x642, ~45 s a launch), and a seeded 480x360
|
||||
window gave uniform white captures (run 41727418224); its "Capture diagnostics" step launches `off`
|
||||
once per window configuration and prints each picture's statistics. Goldens are a second pass: `app/test/golden/gl/<os>/<preset>.png` is
|
||||
compared and reported (not gated) when present; the `refresh_goldens` dispatch input collects
|
||||
candidates into the artifact.
|
||||
|
||||
|
||||
Reference in new issue
Block a user