@@ -1706,6 +1706,24 @@ static inline gboolean _skip_piece_on_tags(const dt_dev_pixelpipe_iop_t *piece)
17061706 && dt_pipe_is_basic (piece -> pipe );
17071707}
17081708
1709+ // The HDR viewer must only receive the fully-processed, display-referred image.
1710+ // The basic darkroom pipes skip enabled modules located downstream of a focused
1711+ // module that shares its operation tags (see dt_iop_module_is_skipped), so the
1712+ // focused module's effect is shown in context. That can drop filmic/sigmoid and
1713+ // leave an unbounded scene-linear buffer at colorout's input. Detect it so we
1714+ // forward only complete frames and otherwise keep the viewer's last good frame.
1715+ static gboolean _hdr_viewer_pipe_incomplete (const dt_dev_pixelpipe_t * pipe ,
1716+ const dt_develop_t * dev )
1717+ {
1718+ for (const GList * n = pipe -> nodes ; n ; n = g_list_next (n ))
1719+ {
1720+ const dt_dev_pixelpipe_iop_t * p = (const dt_dev_pixelpipe_iop_t * )n -> data ;
1721+ if (p && p -> module && p -> enabled && dt_iop_module_is_skipped (dev , p -> module ))
1722+ return TRUE;
1723+ }
1724+ return FALSE;
1725+ }
1726+
17091727static inline gboolean _dev_pixelpipe_early_exit (const dt_develop_t * dev ,
17101728 const dt_dev_pixelpipe_t * pipe )
17111729{
@@ -1745,10 +1763,6 @@ static gboolean _dev_pixelpipe_process_rec(dt_dev_pixelpipe_t *pipe,
17451763 dt_iop_module_t * module = NULL ;
17461764 dt_dev_pixelpipe_iop_t * piece = NULL ;
17471765
1748- // tracks whether the host `input` buffer holds valid pixels (it may be left
1749- // GPU-only after an OpenCL run); used by the HDR viewer tap below.
1750- gboolean input_host_valid = TRUE;
1751-
17521766 const dt_iop_module_t * gui_module = dt_dev_gui_module ();
17531767 // if a module is active, check if this module allow a fast pipe run
17541768 if (gui_module
@@ -2002,6 +2016,75 @@ static gboolean _dev_pixelpipe_process_rec(dt_dev_pixelpipe_t *pipe,
20022016 g_list_previous (pieces ), pos - 1 ))
20032017 return TRUE;
20042018
2019+ // HDR viewer: forward the working-space linear image (colorout's input) to the
2020+ // external HDR preview app. We capture it HERE -- right after the recursion
2021+ // fills `input` and BEFORE colorout processes -- because colorout overwrites/
2022+ // recycles its input buffer in place while converting to the display profile;
2023+ // reading it afterwards yields scratch values, not the tone-mapped image. This
2024+ // buffer is the fully-edited image still in the working profile's linear
2025+ // primaries (Rec.2020 by default) with super-white (>1.0) intact, before
2026+ // colorout bakes in the display TRC -- exactly what an EDR/HDR display needs.
2027+ // The work-profile RGB->XYZ(D50) matrix is sent alongside for color management.
2028+ // NOTE: the socket calls below must remain outside any OMP parallel section.
2029+ if (dev -> gui_attached && !dev -> gui_leaving
2030+ && pipe == dev -> preview_pipe
2031+ && dt_iop_module_is (module , "colorout" )
2032+ && input_format -> datatype == TYPE_FLOAT && input_format -> channels == 4
2033+ && dt_conf_get_bool ("plugins/darkroom/hdr_viewer_enabled" )
2034+ // only forward fully-processed frames: when a tag-filtering module is
2035+ // focused, the basic pipe skips downstream modules (e.g. filmic/sigmoid)
2036+ // and colorout's input would be incomplete (scene-linear).
2037+ && !_hdr_viewer_pipe_incomplete (pipe , dev ))
2038+ {
2039+ // Cooldown: skip connect attempts for 2 seconds after a failed connect to
2040+ // avoid a 200ms timeout on every frame when the viewer is not running. The
2041+ // preview pipe runs on a single thread, so no synchronisation is needed.
2042+ static int64_t _hdr_viewer_next_attempt_us = 0 ;
2043+ const int64_t now_us = g_get_monotonic_time ();
2044+ if (now_us >= _hdr_viewer_next_attempt_us )
2045+ {
2046+ const int viewer_fd = dt_hdr_viewer_connect ();
2047+ if (viewer_fd >= 0 )
2048+ {
2049+ const size_t w = (size_t )roi_in .width ;
2050+ const size_t h = (size_t )roi_in .height ;
2051+ const float * const rgba = (const float * const )input ;
2052+
2053+ // working RGB -> XYZ(D50): darktable stores this as a [4][4]
2054+ // dt_colormatrix_t (rows padded to 4 for SIMD); copy the 3x3 core.
2055+ const dt_iop_order_iccprofile_info_t * const work_profile
2056+ = dt_ioppr_get_pipe_work_profile_info (pipe );
2057+ float rgb_to_xyz [9 ];
2058+ if (work_profile )
2059+ for (int i = 0 ; i < 3 ; i ++ )
2060+ for (int j = 0 ; j < 3 ; j ++ )
2061+ rgb_to_xyz [i * 3 + j ] = work_profile -> matrix_in [i ][j ];
2062+
2063+ // Strip alpha channel: RGBA float -> RGB float (packed, row-major)
2064+ const size_t npixels = w * h ;
2065+ float * rgb = work_profile ? dt_alloc_align_float (npixels * 3 ) : NULL ;
2066+ if (rgb )
2067+ {
2068+ DT_OMP_FOR ()
2069+ for (size_t k = 0 ; k < npixels ; k ++ )
2070+ {
2071+ rgb [k * 3 + 0 ] = rgba [k * 4 + 0 ];
2072+ rgb [k * 3 + 1 ] = rgba [k * 4 + 1 ];
2073+ rgb [k * 3 + 2 ] = rgba [k * 4 + 2 ];
2074+ }
2075+ dt_hdr_viewer_send_frame (viewer_fd , (uint32_t )w , (uint32_t )h , rgb , rgb_to_xyz );
2076+ dt_free_align (rgb );
2077+ }
2078+ dt_hdr_viewer_disconnect (viewer_fd );
2079+ }
2080+ else
2081+ {
2082+ // Viewer not reachable -- back off for 2 seconds before retrying.
2083+ _hdr_viewer_next_attempt_us = now_us + 2 * G_USEC_PER_SEC ;
2084+ }
2085+ }
2086+ }
2087+
20052088 const size_t in_bpp = dt_iop_buffer_dsc_to_bpp (input_format );
20062089
20072090 piece -> dsc_out = piece -> dsc_in = * input_format ;
@@ -2774,7 +2857,6 @@ static gboolean _dev_pixelpipe_process_rec(dt_dev_pixelpipe_t *pipe,
27742857 if (valid_input_on_gpu_only )
27752858 {
27762859 dt_dev_pixelpipe_invalidate_cacheline (pipe , input );
2777- input_host_valid = FALSE;
27782860 }
27792861 }
27802862 else
@@ -2952,70 +3034,6 @@ static gboolean _dev_pixelpipe_process_rec(dt_dev_pixelpipe_t *pipe,
29523034 dt_ioppr_get_histogram_profile_info (dev ));
29533035 }
29543036
2955- // HDR viewer: forward the working-space linear image to the external HDR
2956- // preview app (if running). We tap the *input* to the output color profile
2957- // module ("colorout"), which is the fully-edited image still in the working
2958- // profile's linear primaries (Rec.2020 by default) with super-white (>1.0)
2959- // signal intact -- exactly what an EDR/HDR display needs, and before colorout
2960- // bakes in the display TRC and gamma clips to 8-bit. The work-profile
2961- // RGB->XYZ(D50) matrix is sent alongside so the viewer color-manages
2962- // correctly for any working profile.
2963- // NOTE: the socket calls below must remain outside any OMP parallel section.
2964- if (dev -> gui_attached && !dev -> gui_leaving
2965- && pipe == dev -> preview_pipe
2966- && input_host_valid
2967- && dt_iop_module_is (module , "colorout" )
2968- && input_format -> datatype == TYPE_FLOAT && input_format -> channels == 4
2969- && dt_conf_get_bool ("plugins/darkroom/hdr_viewer_enabled" ))
2970- {
2971- // Cooldown: skip connect attempts for 2 seconds after a failed connect
2972- // to avoid a 200ms timeout on every frame when the viewer is not running.
2973- // The preview pipe runs on a single thread, so no synchronisation needed.
2974- static int64_t _hdr_viewer_next_attempt_us = 0 ;
2975- const int64_t now_us = g_get_monotonic_time ();
2976- if (now_us >= _hdr_viewer_next_attempt_us )
2977- {
2978- const int viewer_fd = dt_hdr_viewer_connect ();
2979- if (viewer_fd >= 0 )
2980- {
2981- const size_t w = (size_t )roi_in .width ;
2982- const size_t h = (size_t )roi_in .height ;
2983- const float * const rgba = (const float * const )input ;
2984-
2985- // working RGB -> XYZ(D50): darktable stores this as a [4][4]
2986- // dt_colormatrix_t (rows padded to 4 for SIMD); copy the 3x3 core.
2987- const dt_iop_order_iccprofile_info_t * const work_profile
2988- = dt_ioppr_get_pipe_work_profile_info (pipe );
2989- float rgb_to_xyz [9 ];
2990- if (work_profile )
2991- for (int i = 0 ; i < 3 ; i ++ )
2992- for (int j = 0 ; j < 3 ; j ++ )
2993- rgb_to_xyz [i * 3 + j ] = work_profile -> matrix_in [i ][j ];
2994-
2995- // Strip alpha channel: RGBA float -> RGB float (packed, row-major)
2996- const size_t npixels = w * h ;
2997- float * rgb = work_profile ? dt_alloc_align_float (npixels * 3 ) : NULL ;
2998- if (rgb )
2999- {
3000- DT_OMP_FOR ()
3001- for (size_t k = 0 ; k < npixels ; k ++ )
3002- {
3003- rgb [k * 3 + 0 ] = rgba [k * 4 + 0 ];
3004- rgb [k * 3 + 1 ] = rgba [k * 4 + 1 ];
3005- rgb [k * 3 + 2 ] = rgba [k * 4 + 2 ];
3006- }
3007- dt_hdr_viewer_send_frame (viewer_fd , (uint32_t )w , (uint32_t )h , rgb , rgb_to_xyz );
3008- dt_free_align (rgb );
3009- }
3010- dt_hdr_viewer_disconnect (viewer_fd );
3011- }
3012- else
3013- {
3014- // Viewer not reachable -- back off for 2 seconds before retrying
3015- _hdr_viewer_next_attempt_us = now_us + 2 * G_USEC_PER_SEC ;
3016- }
3017- }
3018- }
30193037 return _pipe_has_shutdown (pipe );
30203038}
30213039
0 commit comments