Skip to content

feat(videoout): write pad and present telemetry to APS5_VIDEOOUT_TELEMETRY_DIR - #7298

Merged
adriadam10 merged 3 commits into
boykopovar:mainfrom
eliyarson:feat/videoout-telemetry
Oct 19, 2026
Merged

adriadam10 merged 3 commits into
boykopovar:mainfrom
eliyarson:feat/videoout-telemetry

Conversation

@eliyarson

Copy link
Copy Markdown
Contributor

What

Adds APS5_VIDEOOUT_TELEMETRY_DIR=<directory>, which writes <directory>/videoout.jsonl (appended if it exists, one JSON object per line) so that the input a title receives and the presents it makes can be lined up on one clock. A scripted or automated play test can then say what host controller input the title was given at each frame; nothing here feeds input.

Four line shapes, each starting with "v":1, the host steady-clock nanoseconds in t_ns and the event in ev:

  • {"v":1,"t_ns":<ns>,"ev":"contract","contract":"v1-pad","commit":"<12 hex>","pid":<pid>,"prx":"libSceVideoOut"} is the first line of a run: the source commit the libraries were built from (unknown without Git at configuration) and the process.
  • {"v":1,"t_ns":<ns>,"ev":"pad","n":<present>,"buttons":<bits>,"sticks":[<left x>,<left y>,<right x>,<right y>],"triggers":[<L2>,<R2>]} is written when the published pad state changes in its buttons, sticks, L2 or R2. n is the number of presents completed when the change was published, the count the window title shows; motion, touch and the device kind are not compared.
  • {"v":1,"t_ns":<ns>,"ev":"present","n":<present>,"fps":<rate>} is written after the first present and every 30 presents after that, with the count and rate the window title reports.
  • {"v":1,"t_ns":<ns>,"ev":"drops","dropped":<count>} reports the number of lines the writer has dropped so far, every ten seconds while the count grows, and once at a normal exit, only when lines were dropped. An exit that skips static destructors writes no last line. A pad line the full queue refused counts as dropped even though a later publish writes the change.

src/Telemetry.cpp and include/Telemetry.hpp hold a BoundedLineQueue of 4096 lines and one writer thread that batches whatever is queued every 200 ms, so neither the present nor the input path waits for the file. A pad change that the full queue refused stays pending and is written by a later publish (PadChangeFilter::Changed is only committed after the line is in the queue); a present line is dropped rather than blocking. DisplayWindow::frameNum moves from a function-static in UpdateTitle to a member so the pad path can read it, and PadInput::Update takes the window for the same count. tests/Telemetry.cpp covers the change filter (motion and touch are not changes), the full-queue retry and the drops line it produces, that an unset or empty variable opens nothing, the queue bound and drop count, the line shapes of the contract, pad and present events, and that the frame count still agrees with the title.

Documented in docs/user/USAGE.md (Video-out and pad telemetry) and listed in docs/dev/TRACING.md (Performance captures).

Overlap with what is already there: APS5_TRACE_* records presents inside libSceAgcDriver as a Chrome trace and a summary line, without the pad state; APS5_PAD_INPUT_FILE in libScePad feeds scripted input in but records nothing about what the title read; the window title already prints the rate and the frame count, and the present line only copies those numbers into the file. Open PR #7080 adds per-interval presents, frame times and host identity to the libSceAgcDriver summary and bench file, which is a different file and switch and does not carry pad state.

Without the variable nothing is opened and no thread is started: Instance() is a function-local static holding a null pointer, so a present or input update costs one call that checks a guard variable and returns null, and nothing is formatted. From reading the code, not measured.

Field run

Blasphemous 2 (PPSA13579, content 01.007.000), three launches on Linux with a libSceVideoOut.prx built from this branch. The library was built from the commit before a later documentation-only amend (one sentence in docs/user/USAGE.md, no code change), so its contract line names 019a48e81b19; the code it was built from is this commit's.

run present lines pad lines drops lines wall time
r1, cold shader cache 1214 0 0 615.7 s
r2, warm 1166 0 0 586.4 s
r3, warm 1166 0 0 586.3 s

No drops line in any run (about two lines a second, all present). The first three lines of run 2's file, verbatim and consecutive:

{"v":1,"t_ns":10660159495401,"ev":"contract","contract":"v1-pad","commit":"019a48e81b19","pid":1730278,"prx":"libSceVideoOut"}
{"v":1,"t_ns":10660165191777,"ev":"present","n":1,"fps":0.00}
{"v":1,"t_ns":10661116071268,"ev":"present","n":30,"fps":0.00}

The present rate is the number the window title shows, copied into the line: runs 2 and 3 held 59.94 in the title, the intro and two 60 s gameplay windows, and the only dip was one 0.75 s present interval at the New Game load, where the rate fell to 54.5 and recovered in about 3.5 s.

A pad line records the controller and keyboard/mouse state read through SDL; input injected with APS5_PAD_INPUT_FILE is applied later, in libScePad, so a scripted run writes no pad line.

Tested

CachyOS, Linux 7.2.9, GCC 16.2.1, NVIDIA GeForce RTX 3080 Ti on driver 615.78.08. libSceVideoOut and every video_out test target build; ctest -R video_out gives 5 passed and 20 skipped of 25, 0 failed — every test that opens a window skips without a display, so of the new pair video_out_telemetry ran (its change filter, full-queue retry and the drops line it produces, queue bound and drop count, the contract, pad and present line shapes) and video_out_telemetry_caption, which compares the frame count with the window title, skipped here and needs a display. python3 tools/check_conventions.py --base origin/main reports no findings. The cost with the variable unset is from reading the code path, not measured; no frame rate was compared with the variable unset and set. Not tested: Windows and macOS, neither of which was built here.

Checklist

  • Based on current main; no other open PR implements the same functions
  • One topic per PR; follow-ups go in a new PR
  • No comments in code except technical debt
  • Unimplemented paths throw (NotImplemented_nid_no_patch); silent stubs are listed under Silent stubs in their library's technical debt file
  • No notes, investigation .md files or images added to the repository (attach them to this PR)
  • New third-party code is a submodule built from source
  • Generic behaviour, not specific to one title
  • Depends on: none
  • Unblocks: none
  • GPUs tested: NVIDIA GeForce RTX 3080 Ti, driver 615.78.08, CachyOS (Linux); the switch does not use the GPU
  • AI-assisted: yes

@github-actions

github-actions Bot commented Oct 17, 2026 •

Copy link
Copy Markdown

Other open pull requests touch the same code. Merging one will break the other, or both implement the same thing. If this one builds on them, list them in Depends on.

Pull request Conflicting files (lines on main) Same exports Same new files
PR 7881 core/libs/prx/libSceVideoOut/CMakeLists.txt L22-23
core/libs/prx/libSceVideoOut/include/PadInput.hpp L7-8, L22, L32-37
core/libs/prx/libSceVideoOut/src/PadInput.cpp L203-208, L214, L219, L234, L237-238, L244, L248, L251-252
PR 7955 core/libs/prx/libSceVideoOut/src/DisplayWindow.cpp L109-110

Some comparisons could not be completed because a pull request conflicts with the base branch. Review these shared files manually before concluding that there is no overlap.

Pull request Shared files not compared
PR 674 core/libs/prx/libSceVideoOut/CMakeLists.txt
core/libs/prx/libSceVideoOut/include/PadInput.hpp
core/libs/prx/libSceVideoOut/src/PadInput.cpp
core/libs/prx/libSceVideoOut/src/VideoOutDriver.cpp
PR 5021 core/libs/prx/libSceVideoOut/CMakeLists.txt
core/libs/prx/libSceVideoOut/src/VideoOutDriver.cpp
docs/user/USAGE.md
PR 5431 docs/dev/ENVIRONMENT.md
PR 5925 docs/user/USAGE.md
PR 6044 core/libs/prx/libSceVideoOut/CMakeLists.txt
PR 6636 docs/dev/ENVIRONMENT.md
PR 6788 docs/user/USAGE.md
PR 6879 core/libs/prx/libSceVideoOut/src/VideoOutDriver.cpp
PR 6946 docs/dev/ENVIRONMENT.md
docs/user/USAGE.md
PR 6947 docs/dev/ENVIRONMENT.md
docs/user/USAGE.md
PR 6977 core/libs/prx/libSceVideoOut/CMakeLists.txt
PR 7201 core/libs/prx/libSceVideoOut/CMakeLists.txt
core/libs/prx/libSceVideoOut/src/DisplayWindow.cpp
core/libs/prx/libSceVideoOut/src/VideoOutDriver.cpp
PR 7332 core/libs/prx/libSceVideoOut/src/DisplayWindow.cpp
core/libs/prx/libSceVideoOut/src/VideoOutDriver.cpp
docs/dev/ENVIRONMENT.md
docs/user/USAGE.md
PR 7417 docs/dev/ENVIRONMENT.md
docs/dev/TRACING.md
docs/user/USAGE.md
PR 7427 docs/dev/ENVIRONMENT.md
PR 7484 docs/dev/ENVIRONMENT.md
PR 7499 core/libs/prx/libSceVideoOut/src/PadInput.cpp
PR 7604 docs/dev/ENVIRONMENT.md
docs/dev/TRACING.md
PR 7642 docs/dev/ENVIRONMENT.md
docs/user/USAGE.md
PR 7800 docs/user/USAGE.md

@oneandonlydean

Copy link
Copy Markdown
Collaborator

Thanks, this reads well, and it stays cheap when the variable is unset. One CI blocker: the Linux job runs tools/check_skips.py on ctest.xml after the tests and fails the job for any skip whose output doesn't match its DEVICE_LIMITATION pattern. Under CI's offscreen SDL driver, video_out_telemetry_caption skips with skipped, no window: DisplayWindow: ... (offscreen reports 0x0 usable bounds and has no Vulkan surface), and that text doesn't match, so the Linux job would fail with every test passing.

Could you change both skip messages in tests/Telemetry.cpp to the matching form, e.g. skipped, no display or Vulkan device: %s?

@cadenasynyster

cadenasynyster commented Oct 17, 2026 •

Copy link
Copy Markdown
Contributor

Windows run, since the description lists Windows as not built.

Windows 11 10.0.26200.9711, WinLibs GCC 15.2.0 r7, Release, RTX 5070 Ti, real 2560x1440 display, DualSense over Bluetooth. Head b92caf4 merged onto main b51c65d (clean merge).

Library: builds with no new errors. With APS5_VIDEOOUT_TELEMETRY_DIR set and the DualSense in use (video_out_flip_tests.exe eop_aftergpu, about 30 s window):

run lines pad present drops
buttons, both sticks, L2/R2 1653 1651 1 0
controller on, untouched 5 3 1 0

Pad lines carry buttons, sticks and triggers as expected, and an idle controller does not produce a stream of stick-jitter lines (the 3 idle lines are the initial state plus one press and release). There's only one present line because eop_aftergpu hits the known EOP flip bug (#6616) and never completes a second flip, so this run doesn't exercise the every-30-presents line.

Tests: video_out_telemetry and video_out_telemetry_caption are registered under if(BUILD_TESTING AND NOT WIN32), so they never build on Windows. With that changed to if(BUILD_TESTING), the only build error is in tests/Telemetry.cpp:

tests/Telemetry.cpp:112:77: error: cannot convert 'const std::filesystem::__cxx11::path::value_type*' {aka 'const wchar_t*'} to 'const char*'

std::filesystem::path::c_str() is wchar_t on Windows. With OpenFromEnvironment(directory.string().c_str(), "abc123") it builds, and run directly (libraries on PATH):

  • video_out_telemetry_tests: VideoOut telemetry tests passed
  • video_out_telemetry_tests caption with a real window: VideoOut caption counter test passed

So the Windows exclusion can probably go. If it does, the Windows registration would also need the ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>" that the other Windows VideoOut tests use, so the libraries are found under ctest. I only ran the binaries directly, not through ctest.

Not checked: macOS, a title, and frame rate with the variable unset vs set.

AI-assisted: yes

@adriadam10 adriadam10 self-assigned this Oct 19, 2026
eliyarson and others added 3 commits October 19, 2026 15:27
…METRY_DIR

APS5_VIDEOOUT_TELEMETRY_DIR=<directory> appends one JSON object per line to <directory>/videoout.jsonl: a contract line for the run (commit and pid), a pad line whenever the published pad state changes in its buttons, sticks, L2 or R2, a present line after the first present and every 30 after that with the count and rate the window title shows, and a drops line with the number of lines the writer discarded, reported every ten seconds while it grows and once at a normal exit. A bounded queue of 4096 lines and one writer thread batch the file, so neither the present nor the input path waits for it; a pad change that the full queue refused stays pending for a later publish, and the frame count moves from UpdateTitle's function-static to DisplayWindow so the input path can name the present it belongs to.

Without the variable no file is opened and no thread is started: a present or an input update costs one call that checks a guard variable and returns null.
@adriadam10
adriadam10 force-pushed the feat/videoout-telemetry branch from b92caf4 to f185afe Compare October 19, 2026 13:30
@adriadam10
adriadam10 merged commit 965dc2d into boykopovar:main Oct 19, 2026
10 checks passed
@adriadam10

Copy link
Copy Markdown
Collaborator

Added two commits on top:

  • docs/dev/ENVIRONMENT.md: a row for APS5_VIDEOOUT_TELEMETRY_DIR, which tools/check_conventions.py required ([env-reference]).
  • tests/Telemetry.cpp: both skip messages now read skipped, no display or Vulkan device: ..., as @oneandonlydean asked, so tools/check_skips.py accepts the offscreen skip of video_out_telemetry_caption in the Linux job.

Checked: queue and pad-change retry logic against the tests, no code comments, test registered for non-Windows only, docs match the line shapes, and the Windows run by @cadenasynyster; merged.

iamthehimansh added a commit to iamthehimansh/AnyPS5 that referenced this pull request Oct 19, 2026
One conflict, in libSceVideoOut DisplayWindow.cpp: upstream's new
present telemetry (boykopovar#7298) and the macOS main-thread window-title update
are both kept.

Checked: the macOS build has 0 failed objects, 209/209 converter tests
pass, and the web converter builds and passes tools/test_web.py.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants