Skip to content

Screenshots

NuclearMeltdown edited this page Oct 19, 2026 · 7 revisions

Screenshots

src/record/screenshot.cpp, src/render/still_copy.h, src/render/video_renderer.cpp (CopyStill, CopyStillHalf), src/render/display_win32.cpp and display_vulkan.cpp (CopyBackBuffer), src/app_frame.cpp (CollectScreenshots)

F10 by default, Ctrl+F10 for the clipboard. Written at source resolution, after crop, deinterlacing and rotation, and before the interface is drawn — so no toolbar, no statistics overlay, no toast in the picture. Include the interface in the Recording tab moves the grab to the other side of that line; see below.

SDR: PNG and JPEG

Through Windows Imaging Component, which ships with Windows. A screenshot is the one output that should work on a fresh machine where nobody has pressed the ffmpeg download button yet.

CopyStill() queues a copy of the current picture and returns at once. Alpha comes back opaque. It must be called on the render thread.

Nothing waits for the GPU or the disk

Since 6.0 a screenshot costs the render thread about 0.005 ms for the hand-over. Before, the read-back waited for the GPU on the spot: a millisecond or two, which at 240 fps is a whole frame.

  • The press only queues the copy (StillCopy, one per backend).
  • Every frame CollectScreenshots() asks it with TryMap(), which never waits. A frame or so later the GPU is done and the copy is mapped.
  • The bytes are read out of the mapping by the saving thread (StillPixels::Read()): copying eight megabytes on the thread that draws would cost another millisecond.
  • Encoding, writing and the clipboard happen on screenshotJobs_. So does freeing the copy and the bytes, which also takes a millisecond or two.
  • The answer — the toast, a fallback folder — comes back through Collect() on the render thread, the only thread that touches toasts and the config.

Several presses in a row queue several copies, saved in order. At most four are under way at once (kMaxShotsInFlight); a fifth gets Still saving screenshots, so a held key cannot pile them up.

HDR: JPEG XR and AVIF

Only offered when the source is actually HDR and the "keep the range" switch for screenshots is on. See High dynamic range.

CopyStillHalf() queues the picture as linear light, four half floats per pixel, 1.0 meaning diffuse white.

JPEG XR (.jxr)

SaveScreenshotHdr(). Holds scRGB half floats, which is exactly what Windows itself writes for an HDR screenshot — and therefore what the Photos app opens as one rather than as a dark mess.

Costs nothing: Windows ships the encoder, so this is the HDR format that can be written on a machine where nobody has pressed the download button. AVIF, below, is the one screenshot format that cannot.

paperWhiteNits is passed in because scRGB fixes its own 1.0 at eighty nits, so the linear values have to be scaled into that convention.

AVIF

SaveScreenshotAvif(). Ten bit PQ in BT.2020, with the colour description that makes it an HDR image rather than a dark one.

Read by every browser and by most things that are not Windows — but it needs ffmpeg: libaom, inside qBlank through ffmpeg's libraries. Since ffmpeg is already there for recording that is not a new dependency, only a newly required one, and the format picker says so.

Since 6.1 it is encoded in the process, and the tags are set on the encoder directly. Up to 6.0 it went through ffmpeg.exe, whose version 9 no longer carried the PQ curve into the file on that route: the image opened as a flat SDR one.

Why not 16-bit PNG

Nothing would know to read it as HDR. PNG has no place to say "this is on the PQ curve" that any viewer acts on, so a 16-bit PNG of HDR content is a file full of correct numbers that every program displays wrongly.

Including the interface

RenderFrame has two grab points, and the setting decides which one fires.

The default is immediately after renderer_.Draw() and before display_.NewUiFrame(), which is why nothing of the interface can reach the file. The other is after the draw data has been rendered and before display_.EndFrame() — the only moment the back buffer holds the finished window, since under the flip model its contents are undefined once the present has happened.

CopyBackBuffer() queues a copy into a staging texture, about 0.2 ms on the render thread; the saving thread swizzles BGRA to RGBA. Alpha is set rather than carried over: what looks opaque on screen has to be opaque in the file.

Two consequences worth knowing before turning it on:

  • The result is the size of the window, not of the source. It is the scaled picture, letterboxing included, not the source pixels.
  • It is always SDR. With HDR output the back buffer is scRGB in half floats, and converting that to SDR unverified would mean shipping untested colour maths, so the grab is refused, the picture alone is saved instead, and the message says so.

A separate settings window is its own top-level window with its own swapchain, so it does not appear either way. The embedded panel does.

File naming

MakeScreenshotPath() builds a timestamped name in the configured folder, with a counter appended when the same second is hit twice. It creates the folder if necessary and returns empty when it cannot.

MakeHdrScreenshotPath() is the same for the two HDR formats.

The default folder is Pictures\qBlank (DefaultScreenshotFolder()).

To the clipboard

CopyScreenshotToClipboard(). The same grab, handed over as a 24-bit CF_DIB instead of written to a file. It is always the SDR path, even with an HDR source and the range switch on: the clipboard has no way to carry a wide range.

Another program may hold the clipboard for the moment it takes to read it, so opening it is tried five times, 20 ms apart, before The clipboard is busy is reported. That is an answer to come back to, not a failure the user can act on.

Showing the file

The toast after a screenshot, and after a recording (Recording saved), can be clicked: Explorer opens with the file selected (ShowFileInFolder() in src/desktop_win32.cpp). If the file has gone in the meantime, the toast says so instead.

  • These toasts stand 4 s instead of 2.5 s. By the time the mouse reaches the bottom centre, a short one would already be half faded.
  • The toast stays while the mouse rests on it, but only once the mouse has moved there. A mouse that happens to be parked at the bottom centre would otherwise hold it over the picture forever.
  • The first three such toasts say in a second line that they can be clicked. One click ends that for good.
  • They never stack. Since 5.2 two other toasts arriving within 1.5 s stand one under the other instead of the second replacing the first before it could be read — matching the resolution, then the crop that follows it. Only two lines, and not with a file: its click belongs to exactly one message.

SHOpenFolderAndSelectItems reuses an Explorer window that already shows the folder instead of opening another next to it. It wants a single-threaded apartment, though, and the calling thread is multithreaded because of DirectShow, so the call runs on a short thread of its own. If it fails there, explorer /select does the same job with one window more.

Clone this wiki locally