Skip to content

feat(agc,videoout): save the displayed frame as a PNG with a screenshot key - #2501

Closed
dantdmdarealguy wants to merge 2 commits into
boykopovar:mainfrom
dantdmdarealguy:feat/screenshot-hotkey
Closed

dantdmdarealguy wants to merge 2 commits into
boykopovar:mainfrom
dantdmdarealguy:feat/screenshot-hotkey

Conversation

@dantdmdarealguy

Copy link
Copy Markdown
Contributor

What

Adds a screenshot key. F12 in the game window saves the next displayed frame as a PNG at the display buffer's resolution, without the window's scaling or letterbox. Files go to anyps5-screenshots/ beside the executable, or to ANYPS5_SCREENSHOTS, and are named <title ID>-<YYYYMMDD-HHMMSS>.png. The key is a new Screenshot action in anyps5-input.ini; like ToggleFullscreen it accepts only KEY: sources.

Today the only way to capture a frame is APS5_DUMP_FRAMES, which writes downscaled BMPs of the first N frames from start-up. That doesn't help with a frame at a particular moment (a rendering bug, a compatibility report), and CONTRIBUTING asks for screenshots in PRs.

How it works:

  • PadInput handles the key-down like ToggleFullscreen (focused game window, no key repeat) and calls the new AgcDriverRequestScreenshot_nid_postfix(), which sets an atomic flag (AgcDriver::Screenshot).
  • VulkanDevice::PresentDisplayBuffer takes the flag and reuses the GPU frame-dump readback that APS5_DUMP_FRAMES already records in the presentation command buffer, at scale 1. This covers the resident (direct blit), detiled, uniform-clear and linear (tilingMode == 1) paths. The readback is written after the slot's fence, on the existing FrameDumpWriter thread, so encoding never blocks the presenter.
  • A request stays pending while the writer still has queued frames, so repeated presses can't overflow the writer's 64 MiB queue (one 4K frame is 33 MiB). A frame dropped because the swapchain is out of date puts the request back. On a frame that APS5_DUMP_FRAMES also dumps, the screenshot waits for the next frame.
  • Encoding uses the existing decoder_png (Decoder::Png::Encode, already used by libScePngEnc) as 8-bit RGB. Alpha is dropped because the presented image is opaque.
  • A screenshot that can't be written (for example an unwritable directory) stops the process through the writer's existing failure path. This follows "every function either does what it is supposed to or throws".

No guest-visible behaviour changes. sceShareCaptureScreenshot is untouched.

Tested

Windows 11, WinLibs GCC 15.2.0 (15.2.0posix-14.0.0-ucrt-r7, as in CI), full build on c9d42619:

  • cmake --build build and cmake --build build --target libs succeed. tools/check_test_registration.py passes.
  • ctest --parallel 4: 546/548 pass, 1 skipped (guest_video_out_wqhd_detection_no_vulkan, expected with a Vulkan device), 1 failed (hardware_oracle). That test exercises the tools/hw-oracle scripts, which this PR doesn't touch. It fails because the ld.lld on my PATH (LLVM 18) rejects the AMDGPU object (unknown file type).
  • agc_screenshot passes: merged requests, ANYPS5_SCREENSHOTS with a directory that doesn't exist yet, title-ID naming, no overwrite within the same second, BGRA to RGB with opaque alpha (decoded back with Decoder::Png::Decode), and rejection of invalid frames.
  • patched_libraries passes, so the patched libSceVideoOut.prx loads with its new AgcDriverRequestScreenshot_nid_postfix import. The second commit was needed for that. Without --exclude-libs for decoder_png, MinGW exported Decoder::Png::* from libSceAgcDriver, libSceVideoOut (which now decodes icon0.png, feat(videoout): use sce_sys/icon0.png as the window icon #1080) bound Decode to it, and the patched library failed to load with error 127.
  • To get a patched libSceVideoOut.prx on current main at all, I applied fix(videoout): remove duplicate VRR exports #2473 locally; it isn't part of this PR. Without it, nid_patcher stops with duplicate patched export name: LibwuIonIBw from feat(libSceVideoOut): register the VRR status event and accept the VRR status call #1339, independent of this change.
  • python3 tools/check_conventions.py --base origin/main passes.
  • Not tested in a title: I don't have a converted title. Please press F12 in a running title, once on the resident path and once with APS5_NO_RESIDENT_PRESENT=1, and check the PNG.

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 in TechnicalDebt
  • 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
  • AI-assisted: yes, some AI assistance was used

…ot key

F12 (the new Screenshot input action) requests a capture of the next
presented display buffer. The presenter records it with the existing
APS5_DUMP_FRAMES GPU readback at full resolution, and the frame dump
writer thread encodes it with decoder_png into anyps5-screenshots/
beside the executable, or ANYPS5_SCREENSHOTS.
MinGW exported every decoder_png symbol from libSceAgcDriver, so
libSceVideoOut, which lists libSceAgcDriver before decoder_png, bound
Decoder::Png::Decode to it; after NID patching the import no longer
resolved and libSceVideoOut.prx failed to load with error 127.
@github-actions github-actions Bot added the intake paused Closed while PR intake is limited to authors with at least 10 PRs merged into main label Oct 9, 2026
@github-actions

github-actions Bot commented Oct 9, 2026

Copy link
Copy Markdown

Thank you for your interest in the project! We are sorry, but we cannot accept more PRs while we work through the existing backlog. This PR has been automatically closed and labeled "intake paused". This is not a judgment of the quality of your changes.

@github-actions

github-actions Bot commented Oct 9, 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
#1046 core/libs/prx/libSceAgcDriver/CMakeLists.txt L673
#1309 core/libs/prx/libSceAgcDriver/CMakeLists.txt L673
#1640 core/libs/prx/libSceAgcDriver/CMakeLists.txt L673
#1959 core/libs/prx/libSceAgcDriver/CMakeLists.txt L673
#2470 core/libs/prx/libSceAgcDriver/CMakeLists.txt L673

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

intake paused Closed while PR intake is limited to authors with at least 10 PRs merged into main

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant