Skip to content

HDR/EDR darkroom preview via an optional external viewer#21358

Draft
MaykThewessen wants to merge 3 commits into
darktable-org:masterfrom
MaykThewessen:upstream-hdr-viewer
Draft

HDR/EDR darkroom preview via an optional external viewer#21358
MaykThewessen wants to merge 3 commits into
darktable-org:masterfrom
MaykThewessen:upstream-hdr-viewer

Conversation

@MaykThewessen

Copy link
Copy Markdown

PR: HDR/EDR darkroom preview via an optional external viewer

Branch: upstream-hdr-viewer (1 commit, +412 / -0)
Base: darktable-org/darktable:master
Refs: #17710, #18078, #20477
Companion app: https://github.com/MaykThewessen/darktable-hdr-viewer


Motivation

darktable's pixelpipe already produces a scene-referred, super-white (> 1.0)
float image, but GTK3/Cairo can only present it clipped to 8-bit SDR, so HDR
highlights are never visible while editing. Full HDR display needs a toolkit
that supports it (GTK4), which is a large, multi-year migration.

This PR adds an optional, low-cost way to see the real HDR image today,
without putting any platform-specific code in the darktable tree.

Design

When the feature is enabled and a companion viewer app is running, the darkroom
preview pipe forwards its working-space linear RGB image to that app over a
Unix domain socket. The companion app (separate repo, platform-specific, e.g. a
macOS Metal window using Extended Dynamic Range) performs the HDR display. When
no viewer is running, connect() fails fast and darktable behaves exactly as
before.

Why this is cheap to carry upstream:

  • No platform code in-tree. common/hdr_viewer.c is ~200 lines of plain
    POSIX sockets. On non-POSIX platforms (Windows) it compiles to inert stubs, so
    there is no platform-specific build logic and nothing to maintain per-OS.
  • Off by default, behind plugins/darkroom/hdr_viewer_enabled.
  • No new dependencies.
  • Fail-fast and self-limiting: a non-blocking connect with a short timeout
    plus a 2-second cooldown means the preview pipe is never stalled when the
    viewer is absent.

What's in the diff

File Purpose
common/hdr_viewer.c / .h POSIX socket client (protocol v2), Windows stubs
develop/pixelpipe_hb.c tap the input to colorout on the preview pipe
data/darktableconfig.xml.in register plugins/darkroom/hdr_viewer_enabled (default off)
src/CMakeLists.txt add common/hdr_viewer.c

The tap

The image is tapped at the input to the output color profile module
(colorout)
: the fully edited image, still in the working profile's linear
primaries (Rec.2020 by default), with HDR signal intact, before the display TRC
and 8-bit clip. The tap is guarded by gui_attached, a host-resident input
buffer (so OpenCL never ships a GPU-only buffer), float/4-channel input, and the
conf flag.

Protocol (v2)

A 60-byte little-endian header — magic DTHV, version, width, height, channels,
transfer (linear) and the working profile's RGB→XYZ(D50) matrix — followed
by the linear float pixels. Sending the matrix lets the viewer color-manage
correctly for any working profile, not just the default.

Testing

  • Built on macOS (Apple Silicon) on current master; also designed to build on
    Linux/BSD (POSIX path) and Windows (stubs).
  • End-to-end verified against the companion viewer: enable with
    --conf plugins/darkroom/hdr_viewer_enabled=true, run the viewer, open an
    image; the preview updates live and super-white highlights are visible on an
    EDR display.

For maintainers

This is deliberately a minimal, opt-in seam rather than a UI change, so it can
coexist with (and outlive) the eventual GTK4 work. Happy to adjust the seam,
naming, or guards to fit darktable's conventions — feedback on the approach is
very welcome before this is considered for merge.

MaykThewessen and others added 2 commits June 21, 2026 22:30
darktable's pixelpipe already computes a scene-referred, super-white (>1.0)
float image, but GTK3/Cairo can only display it clipped to 8-bit SDR, so those
HDR highlights are never visible while editing. Migrating the canvas to a
toolkit with HDR support is a large, long-term effort (GTK4).

This adds an optional, low-cost path to see the real HDR image now, without any
platform-specific code in the darktable tree: when the feature is enabled and a
companion viewer app is running, the darkroom preview pipe forwards its
working-space linear RGB image to that app over a Unix domain socket. The app
(separate, platform-specific, e.g. a macOS Metal/EDR window) does the HDR
display. When the viewer is not running, connect() fails fast and darktable is
unaffected.

- common/hdr_viewer.c/.h: a ~200-line POSIX socket client. Protocol v2 sends a
  versioned header (magic, dims, channels, transfer) plus the working profile's
  RGB->XYZ(D50) matrix, then the linear float pixels, so the viewer can
  color-manage any working profile. On non-POSIX platforms (Windows) the client
  compiles to inert stubs, so no platform-specific build logic is required.
- develop/pixelpipe_hb.c: tap the input to the output color profile module
  ("colorout") on the preview pipe -- the fully edited image still in the
  working profile's linear primaries with HDR signal intact, before the display
  TRC and 8-bit clip. Guarded by gui-attached, host-resident input, and the
  conf flag; a short cooldown avoids repeated connect timeouts when no viewer
  is present.
- registered behind plugins/darkroom/hdr_viewer_enabled (default off).

Companion viewer: https://github.com/MaykThewessen/darktable-hdr-viewer
Addresses darktable-org#17710, darktable-org#18078, darktable-org#20477.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
colorout converts to the display profile in place, overwriting/recycling its own
input buffer. The tap read that buffer at the end of the recursion, AFTER
colorout had run, so it captured post-process scratch (large positive and
negative working-space values) rather than the tone-mapped image; the EDR preview
then showed a blown-out, hue-shifted result regardless of filmic/sigmoid being
enabled.

Move the tap to immediately after the recursion fills `input` and before
colorout's process(), where the buffer still holds the fully-edited,
display-referred working image (filmic/sigmoid output, super-white intact).

Also gate on _hdr_viewer_pipe_incomplete(): the basic preview pipe skips enabled
modules downstream of a focused tag-filtering module (dt_iop_module_is_skipped),
which can drop filmic/sigmoid and leave a scene-linear buffer at colorout's
input; forward only complete frames. Drop the now-unused input_host_valid.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ression)

Relocating the tap before colorout's process() (and dropping input_host_valid)
left the tap reading the host `input` buffer unconditionally. When OpenCL is
enabled and the upstream module ran on GPU, its result is GPU-resident
(cl_mem_input != NULL) and the host buffer is stale, so the viewer received
garbage pixels. Gate the tap on cl_mem_input == NULL (always NULL on non-OpenCL
builds), forwarding only frames whose host buffer actually holds this frame.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant