Files
rustdesk/libs/scrap
rustdesk a7df138fc7 scrap: tone-map HDR desktops to SDR in the Windows capturer
With HDR enabled, Windows composes the desktop as linear scRGB in
R16G16B16A16_FLOAT and places SDR white at the user's "SDR content
brightness" (DISPLAYCONFIG_SDR_WHITE_LEVEL / 1000, e.g. 2.5 for 200
nits) rather than at 1.0. The legacy IDXGIOutput1::DuplicateOutput we
used always converts that surface to BGRA8, and it does so by clipping,
so everything above ~40% linear brightness lands on white. That is the
washed-out / overexposed / high-contrast picture reported for HDR hosts
in #5368, #9951, #12707 and discussion #7652.

Ask for the desktop with IDXGIOutput5::DuplicateOutput1 and the format
list [R16G16B16A16_FLOAT, B8G8R8A8_UNORM]. An SDR desktop still yields
BGRA8 and takes the unchanged path. A float frame is converted on the
GPU by a small pixel shader: divide by the SDR white level, clamp, apply
the sRGB transfer. SDR content round-trips exactly, HDR highlights clip
at white, the same result the local user sees. The white level is read
per output through DisplayConfigGetDeviceInfo and refreshed once a
second, so dragging the Windows slider tracks remotely. The converted
BGRA8 texture feeds both the staging-copy (CPU) path and the vram
(hwcodec texture) path; rotation is unchanged. Toggling HDR mid-session
invalidates the duplication as before, and the recreated capturer
re-detects the format.

The shaders are compiled at runtime from a dynamically loaded
d3dcompiler_47.dll so no new import is added to the binary. If the DLL,
the compile or any D3D object creation fails, a process-wide flag makes
later capturers fall back to the legacy DuplicateOutput, i.e. today's
behaviour.

This deliberately stops at SDR on the controlled side, with no option
and no protocol change, which is how OBS, Sunshine (client without HDR)
and macOS screen capture handle it. Real HDR pass-through would need
10-bit capture, Main10/AV1-10 encoders in the hwcodec fork, colour
metadata on the wire and, decisively, an HDR output path on the
controller: Flutter's desktop external textures are BGRA8888/RGBA8888
only on every platform, so nothing would be visible. When that changes
it should follow the Sunshine/Moonlight pattern: an hdr capability bit
advertised by the controller behind an explicit user toggle, negotiated
like i444.

Type-checked against x86_64-pc-windows-msvc (with and without the vram
feature); not yet run on an HDR machine, which needs Windows 10 1703+
with HDR on.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RLb1dNdhFqUQExJPANjo1F
(cherry picked from commit ca3c2f8ac7d1549a40855411b0539a69c82d7223)
2026-09-02 12:36:48 +08:00
..
2025-01-20 17:59:36 +08:00
2021-03-29 15:59:14 +08:00
2026-06-18 22:37:15 +08:00
2021-07-29 13:33:26 +08:00

Derived from https://github.com/quadrupleslap/scrap

scrap

Scrap records your screen! At least it does if you're on Windows, macOS, or Linux.

Usage

[dependencies]
scrap = "0.5"

Its API is as simple as it gets!

struct Display; /// A screen.
struct Frame; /// An array of the pixels that were on-screen.
struct Capturer; /// A recording instance.

impl Capturer {
    /// Begin recording.
    pub fn new(display: Display) -> io::Result<Capturer>;

    /// Try to get a frame.
    /// Returns WouldBlock if it's not ready yet.
    pub fn frame<'a>(&'a mut self) -> io::Result<Frame<'a>>;

    pub fn width(&self) -> usize;
    pub fn height(&self) -> usize;
}

impl Display {
    /// The primary screen.
    pub fn primary() -> io::Result<Display>;

    /// All the screens.
    pub fn all() -> io::Result<Vec<Display>>;

    pub fn width(&self) -> usize;
    pub fn height(&self) -> usize;
}

impl<'a> ops::Deref for Frame<'a> {
    /// A frame is just an array of bytes.
    type Target = [u8];
}

The Frame Format

  • The frame format is guaranteed to be packed BGRA.
  • The width and height are guaranteed to remain constant.
  • The stride might be greater than the width, and it may also vary between frames.

System Requirements

OS Minimum Requirements
macOS macOS 10.8
Linux XCB + SHM + RandR
Windows DirectX 11.1