LCOV - code coverage report
Current view: top level - src/shaders - colorspace.c (source / functions) Hit Total Coverage
Test: Code coverage Lines: 569 693 82.1 %
Date: 2025-03-29 09:04:10 Functions: 26 28 92.9 %
Legend: Lines: hit not hit | Branches: + taken - not taken # not executed Branches: 272 435 62.5 %

           Branch data     Line data    Source code
       1                 :            : /*
       2                 :            :  * This file is part of libplacebo.
       3                 :            :  *
       4                 :            :  * libplacebo is free software; you can redistribute it and/or
       5                 :            :  * modify it under the terms of the GNU Lesser General Public
       6                 :            :  * License as published by the Free Software Foundation; either
       7                 :            :  * version 2.1 of the License, or (at your option) any later version.
       8                 :            :  *
       9                 :            :  * libplacebo is distributed in the hope that it will be useful,
      10                 :            :  * but WITHOUT ANY WARRANTY; without even the implied warranty of
      11                 :            :  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      12                 :            :  * GNU Lesser General Public License for more details.
      13                 :            :  *
      14                 :            :  * You should have received a copy of the GNU Lesser General Public
      15                 :            :  * License along with libplacebo. If not, see <http://www.gnu.org/licenses/>.
      16                 :            :  */
      17                 :            : 
      18                 :            : #include <math.h>
      19                 :            : 
      20                 :            : #include "cache.h"
      21                 :            : #include "colorspace.h"
      22                 :            : #include "shaders.h"
      23                 :            : 
      24                 :            : #include <libplacebo/shaders/colorspace.h>
      25                 :            : 
      26                 :       2821 : void pl_shader_set_alpha(pl_shader sh, struct pl_color_repr *repr,
      27                 :            :                          enum pl_alpha_mode mode)
      28                 :            : {
      29                 :       2821 :     bool src_has_alpha = repr->alpha == PL_ALPHA_INDEPENDENT ||
      30                 :            :                          repr->alpha == PL_ALPHA_PREMULTIPLIED;
      31                 :       2821 :     bool dst_not_premul = mode == PL_ALPHA_INDEPENDENT ||
      32                 :       2821 :                           mode == PL_ALPHA_NONE;
      33                 :            : 
      34   [ -  +  -  - ]:       2821 :     if (repr->alpha == PL_ALPHA_PREMULTIPLIED && dst_not_premul) {
      35                 :          0 :         GLSL("if (color.a > 1e-6)               \n"
      36                 :            :              "    color.rgb /= vec3(color.a);   \n");
      37                 :          0 :         repr->alpha = PL_ALPHA_INDEPENDENT;
      38                 :            :     }
      39                 :            : 
      40   [ +  +  +  - ]:       2821 :     if (repr->alpha == PL_ALPHA_INDEPENDENT && mode == PL_ALPHA_PREMULTIPLIED) {
      41                 :          8 :         GLSL("color.rgb *= vec3(color.a); \n");
      42                 :          8 :         repr->alpha = PL_ALPHA_PREMULTIPLIED;
      43                 :            :     }
      44                 :            : 
      45         [ -  + ]:       2821 :     if (src_has_alpha && mode == PL_ALPHA_NONE) {
      46                 :          0 :         GLSL("color.a = 1.0; \n");
      47                 :          0 :         repr->alpha = PL_ALPHA_NONE;
      48                 :            :     }
      49                 :       2821 : }
      50                 :            : 
      51                 :            : #ifdef PL_HAVE_DOVI
      52                 :          4 : static inline void reshape_mmr(pl_shader sh, ident_t mmr, bool single,
      53                 :            :                                int min_order, int max_order)
      54                 :            : {
      55         [ +  - ]:          4 :     if (single) {
      56                 :          4 :         GLSL("const uint mmr_idx = 0u; \n");
      57                 :            :     } else {
      58                 :          0 :         GLSL("uint mmr_idx = uint(coeffs.y); \n");
      59                 :            :     }
      60                 :            : 
      61         [ -  + ]:          4 :     assert(min_order <= max_order);
      62         [ -  + ]:          4 :     if (min_order < max_order)
      63                 :          0 :         GLSL("uint order = uint(coeffs.w); \n");
      64                 :            : 
      65                 :          4 :     GLSL("vec4 sigX;                            \n"
      66                 :            :          "s = coeffs.x;                         \n"
      67                 :            :          "sigX.xyz = sig.xxy * sig.yzz;         \n"
      68                 :            :          "sigX.w = sigX.x * sig.z;              \n"
      69                 :            :          "s += dot("$"[mmr_idx + 0].xyz, sig);  \n"
      70                 :            :          "s += dot("$"[mmr_idx + 1], sigX);     \n",
      71                 :            :          mmr, mmr);
      72                 :            : 
      73         [ +  - ]:          4 :     if (max_order >= 2) {
      74         [ -  + ]:          4 :         if (min_order < 2)
      75                 :          0 :             GLSL("if (order >= 2) { \n");
      76                 :            : 
      77                 :          4 :         GLSL("vec3 sig2 = sig * sig;                \n"
      78                 :            :              "vec4 sigX2 = sigX * sigX;             \n"
      79                 :            :              "s += dot("$"[mmr_idx + 2].xyz, sig2); \n"
      80                 :            :              "s += dot("$"[mmr_idx + 3], sigX2);    \n",
      81                 :            :              mmr, mmr);
      82                 :            : 
      83         [ +  - ]:          4 :         if (max_order == 3) {
      84         [ -  + ]:          4 :             if (min_order < 3)
      85                 :          0 :                 GLSL("if (order >= 3 { \n");
      86                 :            : 
      87                 :          4 :             GLSL("s += dot("$"[mmr_idx + 4].xyz, sig2 * sig);   \n"
      88                 :            :                  "s += dot("$"[mmr_idx + 5], sigX2 * sigX);     \n",
      89                 :            :                  mmr, mmr);
      90                 :            : 
      91         [ -  + ]:          4 :             if (min_order < 3)
      92                 :          0 :                 GLSL("} \n");
      93                 :            :         }
      94                 :            : 
      95         [ #  # ]:          0 :         if (min_order < 2)
      96                 :          0 :             GLSL("} \n");
      97                 :            :     }
      98                 :          4 : }
      99                 :            : 
     100                 :            : static inline void reshape_poly(pl_shader sh)
     101                 :            : {
     102                 :          2 :     GLSL("s = (coeffs.z * s + coeffs.y) * s + coeffs.x; \n");
     103                 :          2 : }
     104                 :            : #endif
     105                 :            : 
     106                 :          2 : void pl_shader_dovi_reshape(pl_shader sh, const struct pl_dovi_metadata *data)
     107                 :            : {
     108                 :            : #ifdef PL_HAVE_DOVI
     109   [ +  -  -  + ]:          2 :     if (!sh_require(sh, PL_SHADER_SIG_COLOR, 0, 0) || !data)
     110                 :          0 :         return;
     111                 :            : 
     112                 :          2 :     sh_describe(sh, "reshaping");
     113                 :          2 :     GLSL("// pl_shader_reshape                  \n"
     114                 :            :          "{                                     \n"
     115                 :            :          "vec3 sig;                             \n"
     116                 :            :          "vec4 coeffs;                          \n"
     117                 :            :          "float s;                              \n"
     118                 :            :          "sig = clamp(color.rgb, 0.0, 1.0);     \n");
     119                 :            : 
     120                 :            :     float coeffs_data[8][4];
     121                 :            :     float mmr_packed_data[8*6][4];
     122                 :            : 
     123         [ +  + ]:          8 :     for (int c = 0; c < 3; c++) {
     124                 :            :         const struct pl_reshape_data *comp = &data->comp[c];
     125         [ -  + ]:          6 :         if (!comp->num_pivots)
     126                 :          0 :             continue;
     127                 :            : 
     128         [ -  + ]:          6 :         pl_assert(comp->num_pivots >= 2 && comp->num_pivots <= 9);
     129                 :          6 :         GLSL("s = sig[%d]; \n", c);
     130                 :            : 
     131                 :            :         // Prepare coefficients for GPU
     132                 :            :         bool has_poly = false, has_mmr = false, mmr_single = true;
     133                 :            :         int mmr_idx = 0, min_order = 3, max_order = 1;
     134                 :            :         memset(coeffs_data, 0, sizeof(coeffs_data));
     135         [ +  + ]:         26 :         for (int i = 0; i < comp->num_pivots - 1; i++) {
     136      [ +  +  - ]:         20 :             switch (comp->method[i]) {
     137                 :         16 :             case 0: // polynomial
     138                 :            :                 has_poly = true;
     139                 :         16 :                 coeffs_data[i][3] = 0.0; // order=0 signals polynomial
     140         [ +  + ]:         64 :                 for (int k = 0; k < 3; k++)
     141                 :         48 :                     coeffs_data[i][k] = comp->poly_coeffs[i][k];
     142                 :            :                 break;
     143                 :            : 
     144                 :          4 :             case 1:
     145                 :          4 :                 min_order = PL_MIN(min_order, comp->mmr_order[i]);
     146                 :          4 :                 max_order = PL_MAX(max_order, comp->mmr_order[i]);
     147                 :          4 :                 mmr_single = !has_mmr;
     148                 :            :                 has_mmr = true;
     149                 :          4 :                 coeffs_data[i][3] = (float) comp->mmr_order[i];
     150                 :          4 :                 coeffs_data[i][0] = comp->mmr_constant[i];
     151                 :          4 :                 coeffs_data[i][1] = (float) mmr_idx;
     152         [ +  + ]:         16 :                 for (int j = 0; j < comp->mmr_order[i]; j++) {
     153                 :            :                     // store weights per order as two packed vec4s
     154                 :         12 :                     float *mmr = &mmr_packed_data[mmr_idx][0];
     155                 :         12 :                     mmr[0] = comp->mmr_coeffs[i][j][0];
     156                 :         12 :                     mmr[1] = comp->mmr_coeffs[i][j][1];
     157                 :         12 :                     mmr[2] = comp->mmr_coeffs[i][j][2];
     158                 :         12 :                     mmr[3] = 0.0; // unused
     159                 :         12 :                     mmr[4] = comp->mmr_coeffs[i][j][3];
     160                 :         12 :                     mmr[5] = comp->mmr_coeffs[i][j][4];
     161                 :         12 :                     mmr[6] = comp->mmr_coeffs[i][j][5];
     162                 :         12 :                     mmr[7] = comp->mmr_coeffs[i][j][6];
     163                 :         12 :                     mmr_idx += 2;
     164                 :            :                 }
     165                 :            :                 break;
     166                 :            : 
     167                 :            :             default:
     168                 :          0 :                 pl_unreachable();
     169                 :            :             }
     170                 :            :         }
     171                 :            : 
     172         [ +  + ]:          6 :         if (comp->num_pivots > 2) {
     173                 :            : 
     174                 :            :             // Skip the (irrelevant) lower and upper bounds
     175                 :            :             float pivots_data[7];
     176                 :          2 :             memcpy(pivots_data, comp->pivots + 1,
     177                 :          2 :                    (comp->num_pivots - 2) * sizeof(pivots_data[0]));
     178                 :            : 
     179                 :            :             // Fill the remainder with a quasi-infinite sentinel pivot
     180         [ -  + ]:          2 :             for (int i = comp->num_pivots - 2; i < PL_ARRAY_SIZE(pivots_data); i++)
     181                 :          0 :                 pivots_data[i] = 1e9f;
     182                 :            : 
     183                 :          2 :             ident_t pivots = sh_var(sh, (struct pl_shader_var) {
     184                 :            :                 .data = pivots_data,
     185                 :            :                 .var = {
     186                 :            :                     .name = "pivots",
     187                 :            :                     .type = PL_VAR_FLOAT,
     188                 :            :                     .dim_v = 1,
     189                 :            :                     .dim_m = 1,
     190                 :            :                     .dim_a = PL_ARRAY_SIZE(pivots_data),
     191                 :            :                 },
     192                 :            :             });
     193                 :            : 
     194                 :          2 :             ident_t coeffs = sh_var(sh, (struct pl_shader_var) {
     195                 :            :                 .data = coeffs_data,
     196                 :            :                 .var = {
     197                 :            :                     .name = "coeffs",
     198                 :            :                     .type = PL_VAR_FLOAT,
     199                 :            :                     .dim_v = 4,
     200                 :            :                     .dim_m = 1,
     201                 :            :                     .dim_a = PL_ARRAY_SIZE(coeffs_data),
     202                 :            :                 },
     203                 :            :             });
     204                 :            : 
     205                 :            :             // Efficiently branch into the correct set of coefficients
     206                 :          2 :             GLSL("#define test(i) bvec4(s >= "$"[i])                \n"
     207                 :            :                  "#define coef(i) "$"[i]                            \n"
     208                 :            :                  "coeffs = mix(mix(mix(coef(0), coef(1), test(0)),  \n"
     209                 :            :                  "                 mix(coef(2), coef(3), test(2)),  \n"
     210                 :            :                  "                 test(1)),                        \n"
     211                 :            :                  "             mix(mix(coef(4), coef(5), test(4)),  \n"
     212                 :            :                  "                 mix(coef(6), coef(7), test(6)),  \n"
     213                 :            :                  "                 test(5)),                        \n"
     214                 :            :                  "             test(3));                            \n"
     215                 :            :                  "#undef test                                       \n"
     216                 :            :                  "#undef coef                                       \n",
     217                 :            :                  pivots, coeffs);
     218                 :            : 
     219                 :            :         } else {
     220                 :            : 
     221                 :            :             // No need for a single pivot, just set the coeffs directly
     222                 :          4 :             GLSL("coeffs = "$"; \n", sh_var(sh, (struct pl_shader_var) {
     223                 :            :                 .var = pl_var_vec4("coeffs"),
     224                 :            :                 .data = coeffs_data,
     225                 :            :             }));
     226                 :            : 
     227                 :            :         }
     228                 :            : 
     229                 :            :         ident_t mmr = NULL_IDENT;
     230         [ +  + ]:          6 :         if (has_mmr) {
     231                 :          4 :             mmr = sh_var(sh, (struct pl_shader_var) {
     232                 :            :                 .data = mmr_packed_data,
     233                 :            :                 .var = {
     234                 :            :                     .name = "mmr",
     235                 :            :                     .type = PL_VAR_FLOAT,
     236                 :            :                     .dim_v = 4,
     237                 :            :                     .dim_m = 1,
     238                 :            :                     .dim_a = mmr_idx,
     239                 :            :                 },
     240                 :            :             });
     241                 :            :         }
     242                 :            : 
     243         [ -  + ]:          6 :         if (has_mmr && has_poly) {
     244                 :          0 :             GLSL("if (coeffs.w == 0.0) { \n");
     245                 :            :             reshape_poly(sh);
     246                 :          0 :             GLSL("} else { \n");
     247                 :          0 :             reshape_mmr(sh, mmr, mmr_single, min_order, max_order);
     248                 :          0 :             GLSL("} \n");
     249         [ +  + ]:          6 :         } else if (has_poly) {
     250                 :            :             reshape_poly(sh);
     251                 :            :         } else {
     252         [ -  + ]:          4 :             assert(has_mmr);
     253                 :          4 :             GLSL("{ \n");
     254                 :          4 :             reshape_mmr(sh, mmr, mmr_single, min_order, max_order);
     255                 :          4 :             GLSL("} \n");
     256                 :            :         }
     257                 :            : 
     258                 :         12 :         ident_t lo = sh_var(sh, (struct pl_shader_var) {
     259                 :          6 :             .var = pl_var_float("lo"),
     260                 :          6 :             .data = &comp->pivots[0],
     261                 :            :         });
     262                 :         12 :         ident_t hi = sh_var(sh, (struct pl_shader_var) {
     263                 :          6 :             .var = pl_var_float("hi"),
     264                 :          6 :             .data = &comp->pivots[comp->num_pivots - 1],
     265                 :            :         });
     266                 :          6 :         GLSL("color[%d] = clamp(s, "$", "$"); \n", c, lo, hi);
     267                 :            :     }
     268                 :            : 
     269                 :          2 :     GLSL("} \n");
     270                 :            : #else
     271                 :            :     SH_FAIL(sh, "libplacebo was compiled without support for dolbyvision reshaping");
     272                 :            : #endif
     273                 :            : }
     274                 :            : 
     275                 :        829 : void pl_shader_decode_color(pl_shader sh, struct pl_color_repr *repr,
     276                 :            :                             const struct pl_color_adjustment *params)
     277                 :            : {
     278         [ -  + ]:        829 :     if (!sh_require(sh, PL_SHADER_SIG_COLOR, 0, 0))
     279                 :          0 :         return;
     280                 :            : 
     281                 :        829 :     sh_describe(sh, "color decoding");
     282                 :        829 :     GLSL("// pl_shader_decode_color \n"
     283                 :            :          "{ \n");
     284                 :            : 
     285                 :            :     // Do this first because the following operations are potentially nonlinear
     286                 :        829 :     pl_shader_set_alpha(sh, repr, PL_ALPHA_INDEPENDENT);
     287                 :            : 
     288         [ +  + ]:        829 :     if (repr->sys == PL_COLOR_SYSTEM_XYZ ||
     289                 :            :         repr->sys == PL_COLOR_SYSTEM_DOLBYVISION)
     290                 :            :     {
     291                 :          4 :         ident_t scale = SH_FLOAT(pl_color_repr_normalize(repr));
     292                 :          4 :         GLSL("color.rgb *= vec3("$"); \n", scale);
     293                 :            :     }
     294                 :            : 
     295         [ +  + ]:        829 :     if (repr->sys == PL_COLOR_SYSTEM_XYZ) {
     296                 :          2 :         pl_shader_linearize(sh, &(struct pl_color_space) {
     297                 :            :             .transfer = PL_COLOR_TRC_ST428,
     298                 :            :         });
     299                 :            :     }
     300                 :            : 
     301         [ +  + ]:        829 :     if (repr->sys == PL_COLOR_SYSTEM_DOLBYVISION)
     302                 :          2 :         pl_shader_dovi_reshape(sh, repr->dovi);
     303                 :            : 
     304                 :        829 :     enum pl_color_system orig_sys = repr->sys;
     305                 :        829 :     pl_transform3x3 tr = pl_color_repr_decode(repr, params);
     306                 :            : 
     307         [ +  + ]:        829 :     if (memcmp(&tr, &pl_transform3x3_identity, sizeof(tr))) {
     308                 :        807 :         ident_t cmat = sh_var(sh, (struct pl_shader_var) {
     309                 :        807 :             .var  = pl_var_mat3("cmat"),
     310                 :        807 :             .data = PL_TRANSPOSE_3X3(tr.mat.m),
     311                 :            :         });
     312                 :            : 
     313                 :        807 :         ident_t cmat_c = sh_var(sh, (struct pl_shader_var) {
     314                 :        807 :             .var  = pl_var_vec3("cmat_c"),
     315                 :            :             .data = tr.c,
     316                 :            :         });
     317                 :            : 
     318                 :        807 :         GLSL("color.rgb = "$" * color.rgb + "$"; \n", cmat, cmat_c);
     319                 :            :     }
     320                 :            : 
     321   [ +  +  +  +  :        829 :     switch (orig_sys) {
                -  +  - ]
     322                 :            :     case PL_COLOR_SYSTEM_BT_2020_C:
     323                 :            :         // Conversion for C'rcY'cC'bc via the BT.2020 CL system:
     324                 :            :         // C'bc = (B'-Y'c) / 1.9404  | C'bc <= 0
     325                 :            :         //      = (B'-Y'c) / 1.5816  | C'bc >  0
     326                 :            :         //
     327                 :            :         // C'rc = (R'-Y'c) / 1.7184  | C'rc <= 0
     328                 :            :         //      = (R'-Y'c) / 0.9936  | C'rc >  0
     329                 :            :         //
     330                 :            :         // as per the BT.2020 specification, table 4. This is a non-linear
     331                 :            :         // transformation because (constant) luminance receives non-equal
     332                 :            :         // contributions from the three different channels.
     333                 :          2 :         GLSL("// constant luminance conversion                              \n"
     334                 :            :              "color.br = color.br * mix(vec2(1.5816, 0.9936),               \n"
     335                 :            :              "                          vec2(1.9404, 1.7184),               \n"
     336                 :            :              "                          lessThanEqual(color.br, vec2(0.0))) \n"
     337                 :            :              "           + color.gg;                                        \n");
     338                 :            :         // Expand channels to camera-linear light. This shader currently just
     339                 :            :         // assumes everything uses the BT.2020 12-bit gamma function, since the
     340                 :            :         // difference between 10 and 12-bit is negligible for anything other
     341                 :            :         // than 12-bit content.
     342                 :          2 :         GLSL("vec3 lin = mix(color.rgb * vec3(1.0/4.5),                        \n"
     343                 :            :              "                pow((color.rgb + vec3(0.0993))*vec3(1.0/1.0993), \n"
     344                 :            :              "                    vec3(1.0/0.45)),                             \n"
     345                 :            :              "                lessThanEqual(vec3(0.08145), color.rgb));        \n");
     346                 :            :         // Calculate the green channel from the expanded RYcB, and recompress to G'
     347                 :            :         // The BT.2020 specification says Yc = 0.2627*R + 0.6780*G + 0.0593*B
     348                 :          2 :         GLSL("color.g = (lin.g - 0.2627*lin.r - 0.0593*lin.b)*1.0/0.6780;   \n"
     349                 :            :              "color.g = mix(color.g * 4.5,                                  \n"
     350                 :            :              "              1.0993 * pow(color.g, 0.45) - 0.0993,           \n"
     351                 :            :              "              0.0181 <= color.g);                             \n");
     352                 :            :         break;
     353                 :            : 
     354                 :            :     case PL_COLOR_SYSTEM_BT_2100_PQ:;
     355                 :            :         // Conversion process from the spec:
     356                 :            :         //
     357                 :            :         // 1. L'M'S' = cmat * ICtCp
     358                 :            :         // 2. LMS = linearize(L'M'S')  (EOTF for PQ, inverse OETF for HLG)
     359                 :            :         // 3. RGB = lms2rgb * LMS
     360                 :            :         //
     361                 :            :         // After this we need to invert step 2 to arrive at non-linear RGB.
     362                 :            :         // (It's important we keep the transfer function conversion separate
     363                 :            :         // from the color system decoding, so we have to partially undo our
     364                 :            :         // work here even though we will end up linearizing later on anyway)
     365                 :            : 
     366                 :          2 :         GLSL(// PQ EOTF
     367                 :            :              "color.rgb = pow(max(color.rgb, 0.0), vec3(1.0/%f));           \n"
     368                 :            :              "color.rgb = max(color.rgb - vec3(%f), 0.0)                    \n"
     369                 :            :              "             / (vec3(%f) - vec3(%f) * color.rgb);             \n"
     370                 :            :              "color.rgb = pow(color.rgb, vec3(1.0/%f));                     \n"
     371                 :            :              // LMS matrix
     372                 :            :              "color.rgb = mat3( 3.43661, -0.79133, -0.0259499,              \n"
     373                 :            :              "                 -2.50645,  1.98360, -0.0989137,              \n"
     374                 :            :              "                  0.06984, -0.192271, 1.12486) * color.rgb;   \n"
     375                 :            :              // PQ OETF
     376                 :            :              "color.rgb = pow(max(color.rgb, 0.0), vec3(%f));               \n"
     377                 :            :              "color.rgb = (vec3(%f) + vec3(%f) * color.rgb)                 \n"
     378                 :            :              "             / (vec3(1.0) + vec3(%f) * color.rgb);            \n"
     379                 :            :              "color.rgb = pow(color.rgb, vec3(%f));                         \n",
     380                 :            :              PQ_M2, PQ_C1, PQ_C2, PQ_C3, PQ_M1,
     381                 :            :              PQ_M1, PQ_C1, PQ_C2, PQ_C3, PQ_M2);
     382                 :          2 :         break;
     383                 :            : 
     384                 :            :     case PL_COLOR_SYSTEM_BT_2100_HLG:
     385                 :          2 :         GLSL(// HLG OETF^-1
     386                 :            :              "color.rgb = mix(vec3(4.0) * color.rgb * color.rgb,                \n"
     387                 :            :              "                exp((color.rgb - vec3(%f)) * vec3(1.0/%f))        \n"
     388                 :            :              "                    + vec3(%f),                                   \n"
     389                 :            :              "                lessThan(vec3(0.5), color.rgb));                  \n"
     390                 :            :              // LMS matrix
     391                 :            :              "color.rgb = mat3( 3.43661, -0.79133, -0.0259499,                  \n"
     392                 :            :              "                 -2.50645,  1.98360, -0.0989137,                  \n"
     393                 :            :              "                  0.06984, -0.192271, 1.12486) * color.rgb;       \n"
     394                 :            :             // HLG OETF
     395                 :            :              "color.rgb = mix(vec3(0.5) * sqrt(color.rgb),                      \n"
     396                 :            :              "                vec3(%f) * log(color.rgb - vec3(%f)) + vec3(%f),  \n"
     397                 :            :              "                lessThan(vec3(1.0), color.rgb));                  \n",
     398                 :            :              HLG_C, HLG_A, HLG_B,
     399                 :            :              HLG_A, HLG_B, HLG_C);
     400                 :          2 :         break;
     401                 :            : 
     402                 :          2 :     case PL_COLOR_SYSTEM_DOLBYVISION:;
     403                 :            : #ifdef PL_HAVE_DOVI
     404                 :            :         // Dolby Vision always outputs BT.2020-referred HPE LMS, so hard-code
     405                 :            :         // the inverse LMS->RGB matrix corresponding to this color space.
     406                 :          2 :         pl_matrix3x3 dovi_lms2rgb = {{
     407                 :            :             { 3.06441879, -2.16597676,  0.10155818},
     408                 :            :             {-0.65612108,  1.78554118, -0.12943749},
     409                 :            :             { 0.01736321, -0.04725154,  1.03004253},
     410                 :            :         }};
     411                 :            : 
     412                 :          2 :         pl_matrix3x3_mul(&dovi_lms2rgb, &repr->dovi->linear);
     413                 :          2 :         ident_t mat = sh_var(sh, (struct pl_shader_var) {
     414                 :          2 :             .var = pl_var_mat3("lms2rgb"),
     415                 :          2 :             .data = PL_TRANSPOSE_3X3(dovi_lms2rgb.m),
     416                 :            :         });
     417                 :            : 
     418                 :            :         // PQ EOTF
     419                 :          2 :         GLSL("color.rgb = pow(max(color.rgb, 0.0), vec3(1.0/%f));   \n"
     420                 :            :              "color.rgb = max(color.rgb - vec3(%f), 0.0)            \n"
     421                 :            :              "             / (vec3(%f) - vec3(%f) * color.rgb);     \n"
     422                 :            :              "color.rgb = pow(color.rgb, vec3(1.0/%f));             \n",
     423                 :            :              PQ_M2, PQ_C1, PQ_C2, PQ_C3, PQ_M1);
     424                 :            :         // LMS matrix
     425                 :          2 :         GLSL("color.rgb = "$" * color.rgb; \n", mat);
     426                 :            :         // PQ OETF
     427                 :          2 :         GLSL("color.rgb = pow(max(color.rgb, 0.0), vec3(%f));       \n"
     428                 :            :              "color.rgb = (vec3(%f) + vec3(%f) * color.rgb)         \n"
     429                 :            :              "             / (vec3(1.0) + vec3(%f) * color.rgb);    \n"
     430                 :            :              "color.rgb = pow(color.rgb, vec3(%f));                 \n",
     431                 :            :              PQ_M1, PQ_C1, PQ_C2, PQ_C3, PQ_M2);
     432                 :            :         break;
     433                 :            : #else
     434                 :            :         SH_FAIL(sh, "libplacebo was compiled without support for dolbyvision reshaping");
     435                 :            :         return;
     436                 :            : #endif
     437                 :            : 
     438                 :            :     case PL_COLOR_SYSTEM_UNKNOWN:
     439                 :            :     case PL_COLOR_SYSTEM_RGB:
     440                 :            :     case PL_COLOR_SYSTEM_XYZ:
     441                 :            :     case PL_COLOR_SYSTEM_BT_601:
     442                 :            :     case PL_COLOR_SYSTEM_BT_709:
     443                 :            :     case PL_COLOR_SYSTEM_SMPTE_240M:
     444                 :            :     case PL_COLOR_SYSTEM_BT_2020_NC:
     445                 :            :     case PL_COLOR_SYSTEM_YCGCO:
     446                 :            :         break; // no special post-processing needed
     447                 :            : 
     448                 :            :     case PL_COLOR_SYSTEM_COUNT:
     449                 :          0 :         pl_unreachable();
     450                 :            :     }
     451                 :            : 
     452                 :            :     // Gamma adjustment. Doing this here (in non-linear light) is technically
     453                 :            :     // somewhat wrong, but this is just an aesthetic parameter and not really
     454                 :            :     // meant for colorimetric precision, so we don't care too much.
     455   [ +  +  -  + ]:        829 :     if (params && params->gamma == 0) {
     456                 :            :         // Avoid division by zero
     457                 :          0 :         GLSL("color.rgb = vec3(0.0); \n");
     458         [ +  + ]:        456 :     } else if (params && params->gamma != 1) {
     459                 :          8 :         ident_t gamma = sh_var(sh, (struct pl_shader_var) {
     460                 :          4 :             .var = pl_var_float("gamma"),
     461                 :          4 :             .data = &(float){ 1 / params->gamma },
     462                 :            :         });
     463                 :          4 :         GLSL("color.rgb = pow(max(color.rgb, vec3(0.0)), vec3("$")); \n", gamma);
     464                 :            :     }
     465                 :            : 
     466                 :        829 :     GLSL("}\n");
     467                 :            : }
     468                 :            : 
     469                 :       1107 : void pl_shader_encode_color(pl_shader sh, const struct pl_color_repr *repr)
     470                 :            : {
     471         [ +  - ]:       1107 :     if (!sh_require(sh, PL_SHADER_SIG_COLOR, 0, 0))
     472                 :            :         return;
     473                 :            : 
     474                 :       1107 :     sh_describe(sh, "color encoding");
     475                 :       1107 :     GLSL("// pl_shader_encode_color \n"
     476                 :            :          "{ \n");
     477                 :            : 
     478   [ +  +  +  -  :       1107 :     switch (repr->sys) {
                   -  + ]
     479                 :            :     case PL_COLOR_SYSTEM_BT_2020_C:
     480                 :            :         // Expand R'G'B' to RGB
     481                 :          2 :         GLSL("vec3 lin = mix(color.rgb * vec3(1.0/4.5),                        \n"
     482                 :            :              "                pow((color.rgb + vec3(0.0993))*vec3(1.0/1.0993), \n"
     483                 :            :              "                    vec3(1.0/0.45)),                             \n"
     484                 :            :              "                lessThanEqual(vec3(0.08145), color.rgb));        \n");
     485                 :            : 
     486                 :            :         // Compute Yc from RGB and compress to R'Y'cB'
     487                 :          2 :         GLSL("color.g = dot(vec3(0.2627, 0.6780, 0.0593), lin);     \n"
     488                 :            :              "color.g = mix(color.g * 4.5,                          \n"
     489                 :            :              "              1.0993 * pow(color.g, 0.45) - 0.0993,   \n"
     490                 :            :              "              0.0181 <= color.g);                     \n");
     491                 :            : 
     492                 :            :         // Compute C'bc and C'rc into color.br
     493                 :          2 :         GLSL("color.br = color.br - color.gg;                       \n"
     494                 :            :              "color.br *= mix(vec2(1.0/1.5816, 1.0/0.9936),         \n"
     495                 :            :              "                vec2(1.0/1.9404, 1.0/1.7184),         \n"
     496                 :            :              "                lessThanEqual(color.br, vec2(0.0)));  \n");
     497                 :            :         break;
     498                 :            : 
     499                 :            :     case PL_COLOR_SYSTEM_BT_2100_PQ:;
     500                 :          2 :         GLSL("color.rgb = pow(max(color.rgb, 0.0), vec3(1.0/%f));           \n"
     501                 :            :              "color.rgb = max(color.rgb - vec3(%f), 0.0)                    \n"
     502                 :            :              "             / (vec3(%f) - vec3(%f) * color.rgb);             \n"
     503                 :            :              "color.rgb = pow(color.rgb, vec3(1.0/%f));                     \n"
     504                 :            :              "color.rgb = mat3(0.412109, 0.166748, 0.024170,                \n"
     505                 :            :              "                 0.523925, 0.720459, 0.075440,                \n"
     506                 :            :              "                 0.063965, 0.112793, 0.900394) * color.rgb;   \n"
     507                 :            :              "color.rgb = pow(color.rgb, vec3(%f));                         \n"
     508                 :            :              "color.rgb = (vec3(%f) + vec3(%f) * color.rgb)                 \n"
     509                 :            :              "             / (vec3(1.0) + vec3(%f) * color.rgb);            \n"
     510                 :            :              "color.rgb = pow(color.rgb, vec3(%f));                         \n",
     511                 :            :              PQ_M2, PQ_C1, PQ_C2, PQ_C3, PQ_M1,
     512                 :            :              PQ_M1, PQ_C1, PQ_C2, PQ_C3, PQ_M2);
     513                 :          2 :         break;
     514                 :            : 
     515                 :            :     case PL_COLOR_SYSTEM_BT_2100_HLG:
     516                 :          2 :         GLSL("color.rgb = mix(vec3(4.0) * color.rgb * color.rgb,                \n"
     517                 :            :              "                exp((color.rgb - vec3(%f)) * vec3(1.0/%f))        \n"
     518                 :            :              "                    + vec3(%f),                                   \n"
     519                 :            :              "                lessThan(vec3(0.5), color.rgb));                  \n"
     520                 :            :              "color.rgb = mat3(0.412109, 0.166748, 0.024170,                    \n"
     521                 :            :              "                 0.523925, 0.720459, 0.075440,                    \n"
     522                 :            :              "                 0.063965, 0.112793, 0.900394) * color.rgb;       \n"
     523                 :            :              "color.rgb = mix(vec3(0.5) * sqrt(color.rgb),                      \n"
     524                 :            :              "                vec3(%f) * log(color.rgb - vec3(%f)) + vec3(%f),  \n"
     525                 :            :              "                lessThan(vec3(1.0), color.rgb));                  \n",
     526                 :            :              HLG_C, HLG_A, HLG_B,
     527                 :            :              HLG_A, HLG_B, HLG_C);
     528                 :          2 :         break;
     529                 :            : 
     530                 :          0 :     case PL_COLOR_SYSTEM_DOLBYVISION:
     531                 :          0 :         SH_FAIL(sh, "Cannot un-apply dolbyvision yet (no inverse reshaping)!");
     532                 :          0 :         return;
     533                 :            : 
     534                 :            :     case PL_COLOR_SYSTEM_UNKNOWN:
     535                 :            :     case PL_COLOR_SYSTEM_RGB:
     536                 :            :     case PL_COLOR_SYSTEM_XYZ:
     537                 :            :     case PL_COLOR_SYSTEM_BT_601:
     538                 :            :     case PL_COLOR_SYSTEM_BT_709:
     539                 :            :     case PL_COLOR_SYSTEM_SMPTE_240M:
     540                 :            :     case PL_COLOR_SYSTEM_BT_2020_NC:
     541                 :            :     case PL_COLOR_SYSTEM_YCGCO:
     542                 :            :         break; // no special pre-processing needed
     543                 :            : 
     544                 :            :     case PL_COLOR_SYSTEM_COUNT:
     545                 :          0 :         pl_unreachable();
     546                 :            :     }
     547                 :            : 
     548                 :            :     // Since this is a relatively rare operation, bypass it as much as possible
     549                 :            :     bool skip = true;
     550                 :       1107 :     skip &= PL_DEF(repr->sys, PL_COLOR_SYSTEM_RGB) == PL_COLOR_SYSTEM_RGB;
     551                 :       1107 :     skip &= PL_DEF(repr->levels, PL_COLOR_LEVELS_FULL) == PL_COLOR_LEVELS_FULL;
     552   [ +  +  -  +  :       1107 :     skip &= !repr->bits.sample_depth || !repr->bits.color_depth ||
                   +  - ]
     553                 :            :              repr->bits.sample_depth == repr->bits.color_depth;
     554                 :       1107 :     skip &= !repr->bits.bit_shift;
     555                 :            : 
     556         [ +  + ]:       1107 :     if (!skip) {
     557                 :         23 :         struct pl_color_repr copy = *repr;
     558                 :            :         ident_t xyzscale = NULL_IDENT;
     559         [ +  + ]:         23 :         if (repr->sys == PL_COLOR_SYSTEM_XYZ)
     560                 :          2 :             xyzscale = SH_FLOAT(1.0 / pl_color_repr_normalize(&copy));
     561                 :            : 
     562                 :         23 :         pl_transform3x3 tr = pl_color_repr_decode(&copy, NULL);
     563                 :         23 :         pl_transform3x3_invert(&tr);
     564                 :            : 
     565                 :         23 :         ident_t cmat = sh_var(sh, (struct pl_shader_var) {
     566                 :         23 :             .var  = pl_var_mat3("cmat"),
     567                 :         23 :             .data = PL_TRANSPOSE_3X3(tr.mat.m),
     568                 :            :         });
     569                 :            : 
     570                 :         23 :         ident_t cmat_c = sh_var(sh, (struct pl_shader_var) {
     571                 :         23 :             .var  = pl_var_vec3("cmat_c"),
     572                 :            :             .data = tr.c,
     573                 :            :         });
     574                 :            : 
     575                 :         23 :         GLSL("color.rgb = "$" * color.rgb + "$"; \n", cmat, cmat_c);
     576                 :            : 
     577         [ +  + ]:         23 :         if (repr->sys == PL_COLOR_SYSTEM_XYZ) {
     578                 :          2 :             pl_shader_delinearize(sh, &(struct pl_color_space) {
     579                 :            :                 .transfer = PL_COLOR_TRC_ST428,
     580                 :            :             });
     581                 :          2 :             GLSL("color.rgb *= vec3("$"); \n", xyzscale);
     582                 :            :         }
     583                 :            :     }
     584                 :            : 
     585         [ -  + ]:       1107 :     if (repr->alpha == PL_ALPHA_PREMULTIPLIED)
     586                 :          0 :         GLSL("color.rgb *= vec3(color.a); \n");
     587                 :            : 
     588                 :       1107 :     GLSL("}\n");
     589                 :            : }
     590                 :            : 
     591                 :         18 : static ident_t sh_luma_coeffs(pl_shader sh, const struct pl_color_space *csp)
     592                 :            : {
     593                 :            :     pl_matrix3x3 rgb2xyz;
     594                 :         18 :     rgb2xyz = pl_get_rgb2xyz_matrix(pl_raw_primaries_get(csp->primaries));
     595                 :            : 
     596                 :            :     // FIXME: Cannot use `const vec3` due to glslang bug #2025
     597                 :         18 :     ident_t coeffs = sh_fresh(sh, "luma_coeffs");
     598                 :         18 :     GLSLH("#define "$" vec3("$", "$", "$") \n", coeffs,
     599                 :            :           SH_FLOAT(rgb2xyz.m[1][0]), // RGB->Y vector
     600                 :            :           SH_FLOAT(rgb2xyz.m[1][1]),
     601                 :            :           SH_FLOAT(rgb2xyz.m[1][2]));
     602                 :         18 :     return coeffs;
     603                 :            : }
     604                 :            : 
     605                 :        322 : void pl_shader_linearize(pl_shader sh, const struct pl_color_space *csp)
     606                 :            : {
     607         [ +  - ]:        322 :     if (!sh_require(sh, PL_SHADER_SIG_COLOR, 0, 0))
     608                 :         64 :         return;
     609                 :            : 
     610         [ +  + ]:        322 :     if (csp->transfer == PL_COLOR_TRC_LINEAR)
     611                 :            :         return;
     612                 :            : 
     613                 :            :     float csp_min, csp_max;
     614                 :        320 :     pl_color_space_nominal_luma_ex(pl_nominal_luma_params(
     615                 :            :         .color      = csp,
     616                 :            :         .metadata   = PL_HDR_METADATA_HDR10,
     617                 :            :         .scaling    = PL_HDR_NORM,
     618                 :            :         .out_min    = &csp_min,
     619                 :            :         .out_max    = &csp_max,
     620                 :            :     ));
     621                 :            : 
     622                 :            :     // Note that this clamp may technically violate the definition of
     623                 :            :     // ITU-R BT.2100, which allows for sub-blacks and super-whites to be
     624                 :            :     // displayed on the display where such would be possible. That said, the
     625                 :            :     // problem is that not all gamma curves are well-defined on the values
     626                 :            :     // outside this range, so we ignore it and just clamp anyway for sanity.
     627                 :        320 :     GLSL("// pl_shader_linearize           \n"
     628                 :            :          "color.rgb = max(color.rgb, 0.0); \n");
     629                 :            : 
     630   [ +  +  +  +  :        320 :     switch (csp->transfer) {
          +  +  +  +  +  
          +  +  +  +  +  
                   +  - ]
     631                 :            :     case PL_COLOR_TRC_SRGB:
     632                 :        236 :         GLSL("color.rgb = mix(color.rgb * vec3(1.0/12.92),               \n"
     633                 :            :              "                pow((color.rgb + vec3(0.055))/vec3(1.055), \n"
     634                 :            :              "                    vec3(2.4)),                            \n"
     635                 :            :              "                lessThan(vec3(0.04045), color.rgb));       \n");
     636                 :        236 :         goto scale_out;
     637                 :         18 :     case PL_COLOR_TRC_BT_1886: {
     638                 :         18 :         const float lb = powf(csp_min, 1/2.4f);
     639                 :         18 :         const float lw = powf(csp_max, 1/2.4f);
     640                 :         18 :         const float a = powf(lw - lb, 2.4f);
     641                 :         18 :         const float b = lb / (lw - lb);
     642                 :         18 :         GLSL("color.rgb = "$" * pow(color.rgb + vec3("$"), vec3(2.4)); \n",
     643                 :            :              SH_FLOAT(a), SH_FLOAT(b));
     644                 :            :         return;
     645                 :            :     }
     646                 :            :     case PL_COLOR_TRC_GAMMA18:
     647                 :          2 :         GLSL("color.rgb = pow(color.rgb, vec3(1.8));\n");
     648                 :          2 :         goto scale_out;
     649                 :            :     case PL_COLOR_TRC_GAMMA20:
     650                 :          2 :         GLSL("color.rgb = pow(color.rgb, vec3(2.0));\n");
     651                 :          2 :         goto scale_out;
     652                 :            :     case PL_COLOR_TRC_UNKNOWN:
     653                 :            :     case PL_COLOR_TRC_GAMMA22:
     654                 :          6 :         GLSL("color.rgb = pow(color.rgb, vec3(2.2));\n");
     655                 :          6 :         goto scale_out;
     656                 :            :     case PL_COLOR_TRC_GAMMA24:
     657                 :          2 :         GLSL("color.rgb = pow(color.rgb, vec3(2.4));\n");
     658                 :          2 :         goto scale_out;
     659                 :            :     case PL_COLOR_TRC_GAMMA26:
     660                 :          2 :         GLSL("color.rgb = pow(color.rgb, vec3(2.6));\n");
     661                 :          2 :         goto scale_out;
     662                 :            :     case PL_COLOR_TRC_GAMMA28:
     663                 :          2 :         GLSL("color.rgb = pow(color.rgb, vec3(2.8));\n");
     664                 :          2 :         goto scale_out;
     665                 :            :     case PL_COLOR_TRC_PRO_PHOTO:
     666                 :          2 :         GLSL("color.rgb = mix(color.rgb * vec3(1.0/16.0),              \n"
     667                 :            :              "                pow(color.rgb, vec3(1.8)),               \n"
     668                 :            :              "                lessThan(vec3(0.03125), color.rgb));     \n");
     669                 :          2 :         goto scale_out;
     670                 :            :     case PL_COLOR_TRC_ST428:
     671                 :          4 :         GLSL("color.rgb = vec3(52.37/48.0) * pow(color.rgb, vec3(2.6));\n");
     672                 :          4 :         goto scale_out;
     673                 :            :     case PL_COLOR_TRC_PQ:
     674                 :         36 :         GLSL("color.rgb = pow(color.rgb, vec3(1.0/%f));         \n"
     675                 :            :              "color.rgb = max(color.rgb - vec3(%f), 0.0)        \n"
     676                 :            :              "             / (vec3(%f) - vec3(%f) * color.rgb); \n"
     677                 :            :              "color.rgb = pow(color.rgb, vec3(1.0/%f));         \n"
     678                 :            :              // PQ's output range is 0-10000, but we need it to be relative to
     679                 :            :              // to PL_COLOR_SDR_WHITE instead, so rescale
     680                 :            :              "color.rgb *= vec3(%f);                            \n",
     681                 :            :              PQ_M2, PQ_C1, PQ_C2, PQ_C3, PQ_M1, 10000.0 / PL_COLOR_SDR_WHITE);
     682                 :         36 :         return;
     683                 :          2 :     case PL_COLOR_TRC_HLG: {
     684                 :          2 :         const float y = fmaxf(1.2f + 0.42f * log10f(csp_max / HLG_REF), 1);
     685                 :          2 :         const float b = sqrtf(3 * powf(csp_min / csp_max, 1 / y));
     686                 :            :         // OETF^-1
     687                 :          2 :         GLSL("color.rgb = "$" * color.rgb + vec3("$");                  \n"
     688                 :            :              "color.rgb = mix(vec3(4.0) * color.rgb * color.rgb,        \n"
     689                 :            :              "                exp((color.rgb - vec3(%f)) * vec3(1.0/%f))\n"
     690                 :            :              "                    + vec3(%f),                           \n"
     691                 :            :              "                lessThan(vec3(0.5), color.rgb));          \n",
     692                 :            :              SH_FLOAT(1 - b), SH_FLOAT(b),
     693                 :            :              HLG_C, HLG_A, HLG_B);
     694                 :            :         // OOTF
     695                 :          2 :         GLSL("color.rgb *= 1.0 / 12.0;                                      \n"
     696                 :            :              "color.rgb *= "$" * pow(max(dot("$", color.rgb), 0.0), "$");   \n",
     697                 :            :              SH_FLOAT(csp_max), sh_luma_coeffs(sh, csp), SH_FLOAT(y - 1));
     698                 :            :         return;
     699                 :            :     }
     700                 :            :     case PL_COLOR_TRC_V_LOG:
     701                 :          2 :         GLSL("color.rgb = mix((color.rgb - vec3(0.125)) * vec3(1.0/5.6), \n"
     702                 :            :              "    pow(vec3(10.0), (color.rgb - vec3(%f)) * vec3(1.0/%f)) \n"
     703                 :            :              "              - vec3(%f),                                  \n"
     704                 :            :              "    lessThanEqual(vec3(0.181), color.rgb));                \n",
     705                 :            :              VLOG_D, VLOG_C, VLOG_B);
     706                 :          2 :         return;
     707                 :            :     case PL_COLOR_TRC_S_LOG1:
     708                 :          2 :         GLSL("color.rgb = pow(vec3(10.0), (color.rgb - vec3(%f)) * vec3(1.0/%f)) \n"
     709                 :            :              "            - vec3(%f);                                            \n",
     710                 :            :              SLOG_C, SLOG_A, SLOG_B);
     711                 :          2 :         return;
     712                 :            :     case PL_COLOR_TRC_S_LOG2:
     713                 :          2 :         GLSL("color.rgb = mix((color.rgb - vec3(%f)) * vec3(1.0/%f),      \n"
     714                 :            :              "    (pow(vec3(10.0), (color.rgb - vec3(%f)) * vec3(1.0/%f)) \n"
     715                 :            :              "              - vec3(%f)) * vec3(1.0/%f),                   \n"
     716                 :            :              "    lessThanEqual(vec3(%f), color.rgb));                    \n",
     717                 :            :              SLOG_Q, SLOG_P, SLOG_C, SLOG_A, SLOG_B, SLOG_K2, SLOG_Q);
     718                 :          2 :         return;
     719                 :            :     case PL_COLOR_TRC_LINEAR:
     720                 :            :     case PL_COLOR_TRC_COUNT:
     721                 :            :         break;
     722                 :            :     }
     723                 :            : 
     724                 :          0 :     pl_unreachable();
     725                 :            : 
     726                 :        258 : scale_out:
     727   [ +  -  +  - ]:        258 :     if (csp_max != 1 || csp_min != 0) {
     728                 :        258 :         GLSL("color.rgb = "$" * color.rgb + vec3("$"); \n",
     729                 :            :              SH_FLOAT(csp_max - csp_min), SH_FLOAT(csp_min));
     730                 :            :     }
     731                 :            : }
     732                 :            : 
     733                 :        324 : void pl_shader_delinearize(pl_shader sh, const struct pl_color_space *csp)
     734                 :            : {
     735         [ +  - ]:        324 :     if (!sh_require(sh, PL_SHADER_SIG_COLOR, 0, 0))
     736                 :            :         return;
     737                 :            : 
     738         [ +  + ]:        324 :     if (csp->transfer == PL_COLOR_TRC_LINEAR)
     739                 :            :         return;
     740                 :            : 
     741                 :            :     float csp_min, csp_max;
     742                 :        306 :     pl_color_space_nominal_luma_ex(pl_nominal_luma_params(
     743                 :            :         .color      = csp,
     744                 :            :         .metadata   = PL_HDR_METADATA_HDR10,
     745                 :            :         .scaling    = PL_HDR_NORM,
     746                 :            :         .out_min    = &csp_min,
     747                 :            :         .out_max    = &csp_max,
     748                 :            :     ));
     749                 :            : 
     750                 :        306 :     GLSL("// pl_shader_delinearize \n");
     751         [ +  + ]:        306 :     if (pl_color_space_is_black_scaled(csp) &&
     752         [ +  + ]:        296 :         csp->transfer != PL_COLOR_TRC_HLG &&
     753   [ +  +  +  - ]:        294 :         (csp_max != 1 || csp_min != 0))
     754                 :            :     {
     755                 :        294 :         GLSL("color.rgb = "$" * color.rgb + vec3("$"); \n",
     756                 :            :              SH_FLOAT(1 / (csp_max - csp_min)),
     757                 :            :              SH_FLOAT(-csp_min / (csp_max - csp_min)));
     758                 :            :     }
     759                 :            : 
     760                 :        306 :     GLSL("color.rgb = max(color.rgb, 0.0); \n");
     761                 :            : 
     762   [ +  +  +  +  :        306 :     switch (csp->transfer) {
          +  +  +  +  +  
          +  +  +  +  +  
                   +  - ]
     763                 :            :     case PL_COLOR_TRC_SRGB:
     764                 :        274 :         GLSL("color.rgb = mix(color.rgb * vec3(12.92),                        \n"
     765                 :            :              "                vec3(1.055) * pow(color.rgb, vec3(1.0/2.4))     \n"
     766                 :            :              "                    - vec3(0.055),                              \n"
     767                 :            :              "                lessThanEqual(vec3(0.0031308), color.rgb));     \n");
     768                 :        274 :         return;
     769                 :          2 :     case PL_COLOR_TRC_BT_1886: {
     770                 :          2 :         const float lb = powf(csp_min, 1/2.4f);
     771                 :          2 :         const float lw = powf(csp_max, 1/2.4f);
     772                 :          2 :         const float a = powf(lw - lb, 2.4f);
     773                 :          2 :         const float b = lb / (lw - lb);
     774                 :          2 :         GLSL("color.rgb = pow("$" * color.rgb, vec3(1.0/2.4)) - vec3("$"); \n",
     775                 :            :              SH_FLOAT(1.0 / a), SH_FLOAT(b));
     776                 :            :         return;
     777                 :            :     }
     778                 :            :     case PL_COLOR_TRC_GAMMA18:
     779                 :          2 :         GLSL("color.rgb = pow(color.rgb, vec3(1.0/1.8));\n");
     780                 :          2 :         return;
     781                 :            :     case PL_COLOR_TRC_GAMMA20:
     782                 :          2 :         GLSL("color.rgb = pow(color.rgb, vec3(1.0/2.0));\n");
     783                 :          2 :         return;
     784                 :            :     case PL_COLOR_TRC_UNKNOWN:
     785                 :            :     case PL_COLOR_TRC_GAMMA22:
     786                 :          4 :         GLSL("color.rgb = pow(color.rgb, vec3(1.0/2.2));\n");
     787                 :          4 :         return;
     788                 :            :     case PL_COLOR_TRC_GAMMA24:
     789                 :          2 :         GLSL("color.rgb = pow(color.rgb, vec3(1.0/2.4));\n");
     790                 :          2 :         return;
     791                 :            :     case PL_COLOR_TRC_GAMMA26:
     792                 :          2 :         GLSL("color.rgb = pow(color.rgb, vec3(1.0/2.6));\n");
     793                 :          2 :         return;
     794                 :            :     case PL_COLOR_TRC_GAMMA28:
     795                 :          2 :         GLSL("color.rgb = pow(color.rgb, vec3(1.0/2.8));\n");
     796                 :          2 :         return;
     797                 :            :     case PL_COLOR_TRC_ST428:
     798                 :          4 :         GLSL("color.rgb = pow(color.rgb * vec3(48.0/52.37), vec3(1.0/2.6));\n");
     799                 :          4 :         return;
     800                 :            :     case PL_COLOR_TRC_PRO_PHOTO:
     801                 :          2 :         GLSL("color.rgb = mix(color.rgb * vec3(16.0),                        \n"
     802                 :            :              "                pow(color.rgb, vec3(1.0/1.8)),                 \n"
     803                 :            :              "                lessThanEqual(vec3(0.001953), color.rgb));     \n");
     804                 :          2 :         return;
     805                 :            :     case PL_COLOR_TRC_PQ:
     806                 :          2 :         GLSL("color.rgb *= vec3(1.0/%f);                         \n"
     807                 :            :              "color.rgb = pow(color.rgb, vec3(%f));              \n"
     808                 :            :              "color.rgb = (vec3(%f) + vec3(%f) * color.rgb)      \n"
     809                 :            :              "             / (vec3(1.0) + vec3(%f) * color.rgb); \n"
     810                 :            :              "color.rgb = pow(color.rgb, vec3(%f));              \n",
     811                 :            :              10000 / PL_COLOR_SDR_WHITE, PQ_M1, PQ_C1, PQ_C2, PQ_C3, PQ_M2);
     812                 :          2 :         return;
     813                 :          2 :     case PL_COLOR_TRC_HLG: {
     814                 :          2 :         const float y = fmaxf(1.2f + 0.42f * log10f(csp_max / HLG_REF), 1);
     815                 :          2 :         const float b = sqrtf(3 * powf(csp_min / csp_max, 1 / y));
     816                 :            :         // OOTF^-1
     817                 :          2 :         GLSL("color.rgb *= 1.0 / "$";                                       \n"
     818                 :            :              "color.rgb *= 12.0 * max(1e-6, pow(dot("$", color.rgb), "$")); \n",
     819                 :            :              SH_FLOAT(csp_max), sh_luma_coeffs(sh, csp), SH_FLOAT((1 - y) / y));
     820                 :            :         // OETF
     821                 :          2 :         GLSL("color.rgb = mix(vec3(0.5) * sqrt(color.rgb),                      \n"
     822                 :            :              "                vec3(%f) * log(color.rgb - vec3(%f)) + vec3(%f),  \n"
     823                 :            :              "                lessThan(vec3(1.0), color.rgb));                  \n"
     824                 :            :              "color.rgb = "$" * color.rgb + vec3("$");                          \n",
     825                 :            :              HLG_A, HLG_B, HLG_C,
     826                 :            :              SH_FLOAT(1 / (1 - b)), SH_FLOAT(-b / (1 - b)));
     827                 :            :         return;
     828                 :            :     }
     829                 :            :     case PL_COLOR_TRC_V_LOG:
     830                 :          2 :         GLSL("color.rgb = mix(vec3(5.6) * color.rgb + vec3(0.125),       \n"
     831                 :            :              "                vec3(%f) * log(color.rgb + vec3(%f))       \n"
     832                 :            :              "                    + vec3(%f),                            \n"
     833                 :            :              "                lessThanEqual(vec3(0.01), color.rgb));     \n",
     834                 :            :              VLOG_C / M_LN10, VLOG_B, VLOG_D);
     835                 :          2 :         return;
     836                 :            :     case PL_COLOR_TRC_S_LOG1:
     837                 :          2 :         GLSL("color.rgb = vec3(%f) * log(color.rgb + vec3(%f)) + vec3(%f);\n",
     838                 :            :              SLOG_A / M_LN10, SLOG_B, SLOG_C);
     839                 :          2 :         return;
     840                 :            :     case PL_COLOR_TRC_S_LOG2:
     841                 :          2 :         GLSL("color.rgb = mix(vec3(%f) * color.rgb + vec3(%f),                \n"
     842                 :            :              "                vec3(%f) * log(vec3(%f) * color.rgb + vec3(%f)) \n"
     843                 :            :              "                    + vec3(%f),                                 \n"
     844                 :            :              "                lessThanEqual(vec3(0.0), color.rgb));           \n",
     845                 :            :              SLOG_P, SLOG_Q, SLOG_A / M_LN10, SLOG_K2, SLOG_B, SLOG_C);
     846                 :          2 :         return;
     847                 :            :     case PL_COLOR_TRC_LINEAR:
     848                 :            :     case PL_COLOR_TRC_COUNT:
     849                 :            :         break;
     850                 :            :     }
     851                 :            : 
     852                 :          0 :     pl_unreachable();
     853                 :            : }
     854                 :            : 
     855                 :            : const struct pl_sigmoid_params pl_sigmoid_default_params = { PL_SIGMOID_DEFAULTS };
     856                 :            : 
     857                 :         96 : void pl_shader_sigmoidize(pl_shader sh, const struct pl_sigmoid_params *params)
     858                 :            : {
     859         [ +  - ]:         96 :     if (!sh_require(sh, PL_SHADER_SIG_COLOR, 0, 0))
     860                 :            :         return;
     861                 :            : 
     862         [ -  + ]:         96 :     params = PL_DEF(params, &pl_sigmoid_default_params);
     863         [ +  - ]:         96 :     float center = PL_DEF(params->center, pl_sigmoid_default_params.center);
     864         [ +  - ]:         96 :     float slope  = PL_DEF(params->slope, pl_sigmoid_default_params.slope);
     865                 :            : 
     866                 :            :     // This function needs to go through (0,0) and (1,1), so we compute the
     867                 :            :     // values at 1 and 0, and then scale/shift them, respectively.
     868                 :         96 :     float offset = 1.0 / (1 + expf(slope * center));
     869                 :         96 :     float scale  = 1.0 / (1 + expf(slope * (center - 1))) - offset;
     870                 :            : 
     871                 :         96 :     GLSL("// pl_shader_sigmoidize                                 \n"
     872                 :            :          "color.rgb = clamp(color.rgb, 0.0, 1.0);                 \n"
     873                 :            :          "color.rgb = vec3("$") - vec3("$") *                     \n"
     874                 :            :          "    log(vec3(1.0) / (color.rgb * vec3("$") + vec3("$")) \n"
     875                 :            :          "        - vec3(1.0));                                   \n",
     876                 :            :          SH_FLOAT(center), SH_FLOAT(1.0 / slope),
     877                 :            :          SH_FLOAT(scale), SH_FLOAT(offset));
     878                 :            : }
     879                 :            : 
     880                 :         96 : void pl_shader_unsigmoidize(pl_shader sh, const struct pl_sigmoid_params *params)
     881                 :            : {
     882         [ +  - ]:         96 :     if (!sh_require(sh, PL_SHADER_SIG_COLOR, 0, 0))
     883                 :            :         return;
     884                 :            : 
     885                 :            :     // See: pl_shader_sigmoidize
     886         [ -  + ]:         96 :     params = PL_DEF(params, &pl_sigmoid_default_params);
     887         [ +  - ]:         96 :     float center = PL_DEF(params->center, pl_sigmoid_default_params.center);
     888         [ +  - ]:         96 :     float slope  = PL_DEF(params->slope, pl_sigmoid_default_params.slope);
     889                 :         96 :     float offset = 1.0 / (1 + expf(slope * center));
     890                 :         96 :     float scale  = 1.0 / (1 + expf(slope * (center - 1))) - offset;
     891                 :            : 
     892                 :         96 :     GLSL("// pl_shader_unsigmoidize                                 \n"
     893                 :            :          "color.rgb = clamp(color.rgb, 0.0, 1.0);                    \n"
     894                 :            :          "color.rgb = vec3("$") /                                    \n"
     895                 :            :          "    (vec3(1.0) + exp(vec3("$") * (vec3("$") - color.rgb))) \n"
     896                 :            :          "    - vec3("$");                                           \n",
     897                 :            :          SH_FLOAT(1.0 / scale),
     898                 :            :          SH_FLOAT(slope), SH_FLOAT(center),
     899                 :            :          SH_FLOAT(offset / scale));
     900                 :            : }
     901                 :            : 
     902                 :            : const struct pl_peak_detect_params pl_peak_detect_default_params = { PL_PEAK_DETECT_DEFAULTS };
     903                 :            : const struct pl_peak_detect_params pl_peak_detect_high_quality_params = { PL_PEAK_DETECT_HQ_DEFAULTS };
     904                 :            : 
     905                 :            : static bool peak_detect_params_eq(const struct pl_peak_detect_params *a,
     906                 :            :                                   const struct pl_peak_detect_params *b)
     907                 :            : {
     908                 :         26 :     return a->smoothing_period     == b->smoothing_period     &&
     909         [ +  - ]:         12 :            a->scene_threshold_low  == b->scene_threshold_low  &&
     910   [ +  +  +  - ]:         26 :            a->scene_threshold_high == b->scene_threshold_high &&
     911         [ -  + ]:         12 :            a->percentile           == b->percentile;
     912                 :            :     // don't compare `allow_delayed` because it doesn't change measurement
     913                 :            : }
     914                 :            : 
     915                 :            : enum {
     916                 :            :     // Split the peak buffer into several independent slices to reduce pressure
     917                 :            :     // on global atomics
     918                 :            :     SLICES = 12,
     919                 :            : 
     920                 :            :     // How many bits to use for storing PQ data. Be careful when setting this
     921                 :            :     // too high, as it may overflow `unsigned int` on large video sources.
     922                 :            :     //
     923                 :            :     // The value chosen is enough to guarantee no overflow for an 8K x 4K frame
     924                 :            :     // consisting entirely of 100% 10k nits PQ values, with 16x16 workgroups.
     925                 :            :     PQ_BITS     = 14,
     926                 :            :     PQ_MAX      = (1 << PQ_BITS) - 1,
     927                 :            : 
     928                 :            :     // How many bits to use for the histogram. We bias the histogram down
     929                 :            :     // by half the PQ range (~90 nits), effectively clumping the SDR part
     930                 :            :     // of the image into a single histogram bin.
     931                 :            :     HIST_BITS   = 7,
     932                 :            :     HIST_BIAS   = 1 << (HIST_BITS - 1),
     933                 :            :     HIST_BINS   = (1 << HIST_BITS) - HIST_BIAS,
     934                 :            : 
     935                 :            :     // Convert from histogram bin to (starting) PQ value
     936                 :            : #define HIST_PQ(bin) (((bin) + HIST_BIAS) << (PQ_BITS - HIST_BITS))
     937                 :            : };
     938                 :            : 
     939                 :            : 
     940                 :            : pl_static_assert(PQ_BITS >= HIST_BITS);
     941                 :            : 
     942                 :            : struct peak_buf_data {
     943                 :            :     unsigned frame_wg_count[SLICES]; // number of work groups processed
     944                 :            :     unsigned frame_wg_active[SLICES];// number of active (nonzero) work groups
     945                 :            :     unsigned frame_sum_pq[SLICES];   // sum of PQ Y values over all WGs (PQ_BITS)
     946                 :            :     unsigned frame_max_pq[SLICES];   // maximum PQ Y value among these WGs (PQ_BITS)
     947                 :            :     unsigned frame_hist[SLICES][HIST_BINS]; // always allocated, conditionally used
     948                 :            : };
     949                 :            : 
     950                 :            : static const struct pl_buffer_var peak_buf_vars[] = {
     951                 :            : #define VAR(field) {                                                            \
     952                 :            :     .var = {                                                                    \
     953                 :            :         .name = #field,                                                         \
     954                 :            :         .type = PL_VAR_UINT,                                                    \
     955                 :            :         .dim_v = 1,                                                             \
     956                 :            :         .dim_m = 1,                                                             \
     957                 :            :         .dim_a = sizeof(((struct peak_buf_data *) NULL)->field) /               \
     958                 :            :                  sizeof(unsigned),                                              \
     959                 :            :     },                                                                          \
     960                 :            :     .layout = {                                                                 \
     961                 :            :         .offset = offsetof(struct peak_buf_data, field),                        \
     962                 :            :         .size   = sizeof(((struct peak_buf_data *) NULL)->field),               \
     963                 :            :         .stride = sizeof(unsigned),                                             \
     964                 :            :     },                                                                          \
     965                 :            : }
     966                 :            :     VAR(frame_wg_count),
     967                 :            :     VAR(frame_wg_active),
     968                 :            :     VAR(frame_sum_pq),
     969                 :            :     VAR(frame_max_pq),
     970                 :            :     VAR(frame_hist),
     971                 :            : #undef VAR
     972                 :            : };
     973                 :            : 
     974                 :            : struct sh_color_map_obj {
     975                 :            :     // Tone map state
     976                 :            :     struct {
     977                 :            :         struct pl_tone_map_params params;
     978                 :            :         pl_shader_obj lut;
     979                 :            :     } tone;
     980                 :            : 
     981                 :            :     // Gamut map state
     982                 :            :     struct {
     983                 :            :         pl_shader_obj lut;
     984                 :            :     } gamut;
     985                 :            : 
     986                 :            :     // Peak detection state
     987                 :            :     struct {
     988                 :            :         struct pl_peak_detect_params params;    // currently active parameters
     989                 :            :         pl_buf buf;                             // pending peak detection buffer
     990                 :            :         pl_buf readback;                        // readback buffer (fallback)
     991                 :            :         float avg_pq;                           // current (smoothed) values
     992                 :            :         float max_pq;
     993                 :            :     } peak;
     994                 :            : };
     995                 :            : 
     996                 :            : // Excluding size, since this is checked by sh_lut
     997                 :         30 : static uint64_t gamut_map_signature(const struct pl_gamut_map_params *par)
     998                 :            : {
     999                 :            :     uint64_t sig = CACHE_KEY_GAMUT_LUT;
    1000                 :         30 :     pl_hash_merge(&sig, pl_str0_hash(par->function->name));
    1001                 :         30 :     pl_hash_merge(&sig, pl_var_hash(par->input_gamut));
    1002                 :         30 :     pl_hash_merge(&sig, pl_var_hash(par->output_gamut));
    1003                 :         30 :     pl_hash_merge(&sig, pl_var_hash(par->min_luma));
    1004                 :         30 :     pl_hash_merge(&sig, pl_var_hash(par->max_luma));
    1005                 :         30 :     pl_hash_merge(&sig, pl_var_hash(par->constants));
    1006                 :         30 :     return sig;
    1007                 :            : }
    1008                 :            : 
    1009                 :         11 : static void sh_color_map_uninit(pl_gpu gpu, void *ptr)
    1010                 :            : {
    1011                 :            :     struct sh_color_map_obj *obj = ptr;
    1012                 :         11 :     pl_shader_obj_destroy(&obj->tone.lut);
    1013                 :         11 :     pl_shader_obj_destroy(&obj->gamut.lut);
    1014                 :         11 :     pl_buf_destroy(gpu, &obj->peak.buf);
    1015                 :         11 :     pl_buf_destroy(gpu, &obj->peak.readback);
    1016                 :            :     memset(obj, 0, sizeof(*obj));
    1017                 :         11 : }
    1018                 :            : 
    1019                 :            : static inline float iir_coeff(float rate)
    1020                 :            : {
    1021                 :         14 :     if (!rate)
    1022                 :            :         return 1.0f;
    1023                 :         12 :     return 1.0f - expf(-1.0f / rate);
    1024                 :            : }
    1025                 :            : 
    1026                 :         14 : static float measure_peak(const struct peak_buf_data *data, float percentile)
    1027                 :            : {
    1028                 :         14 :     unsigned frame_max_pq = data->frame_max_pq[0];
    1029         [ +  + ]:        168 :     for (int k = 1; k < SLICES; k++)
    1030                 :        154 :         frame_max_pq = PL_MAX(frame_max_pq, data->frame_max_pq[k]);
    1031                 :         14 :     const float frame_max = (float) frame_max_pq / PQ_MAX;
    1032   [ +  +  -  + ]:         14 :     if (percentile <= 0 || percentile >= 100)
    1033                 :            :         return frame_max;
    1034                 :            :     unsigned total_pixels = 0;
    1035         [ #  # ]:          0 :     for (int k = 0; k < SLICES; k++) {
    1036         [ #  # ]:          0 :         for (int i = 0; i < HIST_BINS; i++)
    1037                 :          0 :             total_pixels += data->frame_hist[k][i];
    1038                 :            :     }
    1039         [ #  # ]:          0 :     if (!total_pixels) // no histogram data available?
    1040                 :            :         return frame_max;
    1041                 :            : 
    1042                 :          0 :     const unsigned target_pixel = ceilf(percentile / 100.0f * total_pixels);
    1043         [ #  # ]:          0 :     if (target_pixel >= total_pixels)
    1044                 :            :         return frame_max;
    1045                 :            : 
    1046                 :            :     unsigned sum = 0;
    1047         [ #  # ]:          0 :     for (int i = 0; i < HIST_BINS; i++) {
    1048                 :            :         unsigned next = sum;
    1049         [ #  # ]:          0 :         for (int k = 0; k < SLICES; k++)
    1050                 :          0 :             next += data->frame_hist[k][i];
    1051         [ #  # ]:          0 :         if (next < target_pixel) {
    1052                 :            :             sum = next;
    1053                 :            :             continue;
    1054                 :            :         }
    1055                 :            : 
    1056                 :            :         // Upper and lower frequency boundaries of the matching histogram bin
    1057                 :            :         const unsigned count_low  = sum;      // last pixel of previous bin
    1058                 :          0 :         const unsigned count_high = next + 1; // first pixel of next bin
    1059         [ #  # ]:          0 :         pl_assert(count_low < target_pixel && target_pixel < count_high);
    1060                 :            : 
    1061                 :            :         // PQ luminance associated with count_low/high respectively
    1062                 :          0 :         const float pq_low  = (float) HIST_PQ(i)     / PQ_MAX;
    1063                 :          0 :         float pq_high       = (float) HIST_PQ(i + 1) / PQ_MAX;
    1064         [ #  # ]:          0 :         if (count_high > total_pixels) // special case for last histogram bin
    1065                 :            :             pq_high = frame_max;
    1066                 :            : 
    1067                 :            :         // Position of `target_pixel` inside this bin, assumes pixels are
    1068                 :            :         // equidistributed inside a histogram bin
    1069                 :          0 :         const float ratio = (float) (target_pixel - count_low) /
    1070                 :          0 :                                     (count_high - count_low);
    1071                 :          0 :         return PL_MIX(pq_low, pq_high, ratio);
    1072                 :            :     }
    1073                 :            : 
    1074                 :          0 :     pl_unreachable();
    1075                 :            : }
    1076                 :            : 
    1077                 :            : // if `force` is true, ensures the buffer is read, even if `allow_delayed`
    1078                 :        794 : static void update_peak_buf(pl_gpu gpu, struct sh_color_map_obj *obj, bool force)
    1079                 :            : {
    1080                 :            :     const struct pl_peak_detect_params *params = &obj->peak.params;
    1081         [ +  + ]:        794 :     if (!obj->peak.buf)
    1082                 :        780 :         return;
    1083                 :            : 
    1084   [ +  +  +  +  :         16 :     if (!force && params->allow_delayed && pl_buf_poll(gpu, obj->peak.buf, 0))
                   +  - ]
    1085                 :            :         return; // buffer not ready yet
    1086                 :            : 
    1087                 :            :     bool ok;
    1088                 :         16 :     struct peak_buf_data data = {0};
    1089         [ -  + ]:         16 :     if (obj->peak.readback) {
    1090                 :          0 :         pl_buf_copy(gpu, obj->peak.readback, 0, obj->peak.buf, 0, sizeof(data));
    1091                 :          0 :         ok = pl_buf_read(gpu, obj->peak.readback, 0, &data, sizeof(data));
    1092                 :            :     } else {
    1093                 :         16 :         ok = pl_buf_read(gpu, obj->peak.buf, 0, &data, sizeof(data));
    1094                 :            :     }
    1095   [ +  -  +  + ]:         16 :     if (ok && data.frame_wg_count[0] > 0) {
    1096                 :            :         // Peak detection completed successfully
    1097                 :         14 :         pl_buf_destroy(gpu, &obj->peak.buf);
    1098                 :            :     } else {
    1099                 :            :         // No data read? Possibly this peak obj has not been executed yet
    1100                 :            :         if (!ok) {
    1101                 :          0 :             PL_ERR(gpu, "Failed reading peak detection buffer!");
    1102         [ +  - ]:          2 :         } else if (params->allow_delayed) {
    1103                 :          2 :             PL_TRACE(gpu, "Peak detection buffer not yet ready, ignoring..");
    1104                 :            :         } else {
    1105                 :          0 :             PL_WARN(gpu, "Peak detection usage error: attempted detecting peak "
    1106                 :            :                     "and using detected peak in the same shader program, "
    1107                 :            :                     "but `params->allow_delayed` is false! Ignoring, but "
    1108                 :            :                     "expect incorrect output.");
    1109                 :            :         }
    1110         [ -  + ]:          2 :         if (force || !ok)
    1111                 :          0 :             pl_buf_destroy(gpu, &obj->peak.buf);
    1112                 :          2 :         return;
    1113                 :            :     }
    1114                 :            : 
    1115                 :            :     uint64_t frame_sum_pq = 0u, frame_wg_count = 0u, frame_wg_active = 0u;
    1116         [ +  + ]:        182 :     for (int k = 0; k < SLICES; k++) {
    1117                 :        168 :         frame_sum_pq    += data.frame_sum_pq[k];
    1118                 :        168 :         frame_wg_count  += data.frame_wg_count[k];
    1119                 :        168 :         frame_wg_active += data.frame_wg_active[k];
    1120                 :            :     }
    1121                 :            :     float avg_pq, max_pq;
    1122         [ +  - ]:         14 :     if (frame_wg_active) {
    1123                 :         14 :         avg_pq = (float) frame_sum_pq / (frame_wg_active * PQ_MAX);
    1124                 :         14 :         max_pq = measure_peak(&data, params->percentile);
    1125                 :            :     } else {
    1126                 :            :         // Solid black frame
    1127                 :            :         avg_pq = max_pq = PL_COLOR_HDR_BLACK;
    1128                 :            :     }
    1129                 :            : 
    1130         [ +  + ]:         14 :     if (!obj->peak.avg_pq) {
    1131                 :            :         // Set the initial value accordingly if it contains no data
    1132                 :          4 :         obj->peak.avg_pq = avg_pq;
    1133                 :          4 :         obj->peak.max_pq = max_pq;
    1134                 :            :     } else {
    1135                 :            :         // Ignore small deviations from existing peak (rounding error)
    1136                 :            :         static const float epsilon = 1.0f / PQ_MAX;
    1137         [ +  - ]:         10 :         if (fabsf(avg_pq - obj->peak.avg_pq) < epsilon)
    1138                 :            :             avg_pq = obj->peak.avg_pq;
    1139         [ +  - ]:         10 :         if (fabsf(max_pq - obj->peak.max_pq) < epsilon)
    1140                 :            :             max_pq = obj->peak.max_pq;
    1141                 :            :     }
    1142                 :            : 
    1143                 :            :     // Use an IIR low-pass filter to smooth out the detected values
    1144         [ +  + ]:         14 :     const float coeff = iir_coeff(params->smoothing_period);
    1145                 :         14 :     obj->peak.avg_pq += coeff * (avg_pq - obj->peak.avg_pq);
    1146                 :         14 :     obj->peak.max_pq += coeff * (max_pq - obj->peak.max_pq);
    1147                 :            : 
    1148                 :            :     // Scene change hysteresis
    1149   [ +  +  +  - ]:         14 :     if (params->scene_threshold_low > 0 && params->scene_threshold_high > 0) {
    1150                 :            :         const float log10_pq = 1e-2f; // experimentally determined approximate
    1151                 :         12 :         const float thresh_low = params->scene_threshold_low * log10_pq;
    1152                 :         12 :         const float thresh_high = params->scene_threshold_high * log10_pq;
    1153                 :         12 :         const float bias = (float) frame_wg_active / frame_wg_count;
    1154         [ -  + ]:         12 :         const float delta = bias * fabsf(avg_pq - obj->peak.avg_pq);
    1155                 :            :         const float mix_coeff = pl_smoothstep(thresh_low, thresh_high, delta);
    1156                 :         12 :         obj->peak.avg_pq = PL_MIX(obj->peak.avg_pq, avg_pq, mix_coeff);
    1157                 :         12 :         obj->peak.max_pq = PL_MIX(obj->peak.max_pq, max_pq, mix_coeff);
    1158                 :            :     }
    1159                 :            : }
    1160                 :            : 
    1161                 :         14 : bool pl_shader_detect_peak(pl_shader sh, struct pl_color_space csp,
    1162                 :            :                            pl_shader_obj *state,
    1163                 :            :                            const struct pl_peak_detect_params *params)
    1164                 :            : {
    1165         [ -  + ]:         14 :     params = PL_DEF(params, &pl_peak_detect_default_params);
    1166         [ -  + ]:         14 :     if (!sh_require(sh, PL_SHADER_SIG_COLOR, 0, 0))
    1167                 :            :         return false;
    1168                 :            : 
    1169                 :         14 :     pl_gpu gpu = SH_GPU(sh);
    1170   [ +  -  -  + ]:         14 :     if (!gpu || gpu->limits.max_ssbo_size < sizeof(struct peak_buf_data)) {
    1171                 :          0 :         PL_ERR(sh, "HDR peak detection requires a GPU with support for at "
    1172                 :            :                "least %zu bytes of SSBO data (supported: %zu)",
    1173                 :            :                sizeof(struct peak_buf_data), gpu ? gpu->limits.max_ssbo_size : 0);
    1174                 :          0 :         return false;
    1175                 :            :     }
    1176                 :            : 
    1177   [ +  +  +  - ]:         14 :     const bool use_histogram = params->percentile > 0 && params->percentile < 100;
    1178                 :            :     size_t shmem_req = 3 * sizeof(uint32_t);
    1179                 :            :     if (use_histogram)
    1180                 :            :         shmem_req += sizeof(uint32_t[HIST_BINS]);
    1181                 :            : 
    1182         [ -  + ]:         14 :     if (!sh_try_compute(sh, 16, 16, true, shmem_req)) {
    1183                 :          0 :         PL_ERR(sh, "HDR peak detection requires compute shaders with support "
    1184                 :            :                "for at least %zu bytes of shared memory! (avail: %zu)",
    1185                 :            :                shmem_req, sh_glsl(sh).max_shmem_size);
    1186                 :          0 :         return false;
    1187                 :            :     }
    1188                 :            : 
    1189                 :            :     struct sh_color_map_obj *obj;
    1190                 :         14 :     obj = SH_OBJ(sh, state, PL_SHADER_OBJ_COLOR_MAP, struct sh_color_map_obj,
    1191                 :            :                  sh_color_map_uninit);
    1192         [ -  + ]:         14 :     if (!obj)
    1193                 :            :         return false;
    1194                 :            : 
    1195                 :            :     if (peak_detect_params_eq(&obj->peak.params, params)) {
    1196                 :         12 :         update_peak_buf(gpu, obj, true); // prevent over-writing previous frame
    1197                 :            :     } else {
    1198                 :          2 :         pl_reset_detected_peak(*state);
    1199                 :            :     }
    1200                 :            : 
    1201         [ -  + ]:         14 :     pl_assert(!obj->peak.buf);
    1202                 :            :     static const struct peak_buf_data zero = {0};
    1203                 :            : 
    1204                 :         14 : retry_ssbo:
    1205         [ -  + ]:         14 :     if (obj->peak.readback) {
    1206                 :          0 :         obj->peak.buf = pl_buf_create(gpu, pl_buf_params(
    1207                 :            :             .size           = sizeof(struct peak_buf_data),
    1208                 :            :             .storable       = true,
    1209                 :            :             .initial_data   = &zero,
    1210                 :            :         ));
    1211                 :            :     } else {
    1212                 :         14 :         obj->peak.buf = pl_buf_create(gpu, pl_buf_params(
    1213                 :            :             .size           = sizeof(struct peak_buf_data),
    1214                 :            :             .memory_type    = PL_BUF_MEM_DEVICE,
    1215                 :            :             .host_readable  = true,
    1216                 :            :             .storable       = true,
    1217                 :            :             .initial_data   = &zero,
    1218                 :            :         ));
    1219                 :            :     }
    1220                 :            : 
    1221   [ -  +  -  - ]:         14 :     if (!obj->peak.buf && !obj->peak.readback) {
    1222                 :          0 :         PL_WARN(sh, "Failed creating host-readable peak detection SSBO, "
    1223                 :            :                 "retrying with fallback buffer");
    1224                 :          0 :         obj->peak.readback = pl_buf_create(gpu, pl_buf_params(
    1225                 :            :             .size           = sizeof(struct peak_buf_data),
    1226                 :            :             .host_readable  = true,
    1227                 :            :         ));
    1228         [ #  # ]:          0 :         if (obj->peak.readback)
    1229                 :          0 :             goto retry_ssbo;
    1230                 :            :     }
    1231                 :            : 
    1232         [ -  + ]:         14 :     if (!obj->peak.buf) {
    1233                 :          0 :         SH_FAIL(sh, "Failed creating peak detection SSBO!");
    1234                 :          0 :         return false;
    1235                 :            :     }
    1236                 :            : 
    1237                 :         14 :     obj->peak.params = *params;
    1238                 :            : 
    1239                 :         14 :     sh_desc(sh, (struct pl_shader_desc) {
    1240                 :            :         .desc = {
    1241                 :            :             .name   = "PeakBuf",
    1242                 :            :             .type   = PL_DESC_BUF_STORAGE,
    1243                 :            :             .access = PL_DESC_ACCESS_READWRITE,
    1244                 :            :         },
    1245                 :            :         .binding.object  = obj->peak.buf,
    1246                 :            :         .buffer_vars     = (struct pl_buffer_var *) peak_buf_vars,
    1247                 :            :         .num_buffer_vars = PL_ARRAY_SIZE(peak_buf_vars),
    1248                 :            :     });
    1249                 :            : 
    1250                 :            :     // For performance, we want to do as few atomic operations on global
    1251                 :            :     // memory as possible, so use an atomic in shmem for the work group.
    1252                 :         14 :     ident_t wg_sum   = sh_fresh(sh, "wg_sum"),
    1253                 :         14 :             wg_max   = sh_fresh(sh, "wg_max"),
    1254                 :         14 :             wg_black = sh_fresh(sh, "wg_black"),
    1255                 :            :             wg_hist  = NULL_IDENT;
    1256                 :         14 :     GLSLH("shared uint "$", "$", "$"; \n", wg_sum, wg_max, wg_black);
    1257         [ -  + ]:         14 :     if (use_histogram) {
    1258                 :          0 :         wg_hist = sh_fresh(sh, "wg_hist");
    1259                 :          0 :         GLSLH("shared uint "$"[%u]; \n", wg_hist, HIST_BINS);
    1260                 :            :     }
    1261                 :            : 
    1262                 :         14 :     sh_describe(sh, "peak detection");
    1263                 :          6 : {
    1264                 :         14 :     const struct __attribute__((__packed__)) {
    1265                 :            :     unsigned slices;
    1266                 :            :     unsigned hist_bins;
    1267                 :            :     ident_t wg_sum;
    1268                 :            :     ident_t wg_max;
    1269                 :            :     ident_t wg_black;
    1270                 :            :     ident_t wg_hist;
    1271                 :            :     bool use_histogram;
    1272                 :         14 : } _glsl_1263_args = {
    1273                 :          0 : #line 1268
    1274                 :            :         .slices = SLICES,
    1275                 :          6 : #line 1269
    1276                 :          6 :         .hist_bins = HIST_BINS,
    1277                 :          6 : #line 1271
    1278                 :            :         .wg_sum = wg_sum,
    1279                 :         14 : #line 1271
    1280         [ -  + ]:         20 :         .wg_max = wg_max,
    1281                 :          6 : #line 1271
    1282                 :         14 :         .wg_black = wg_black,
    1283                 :         14 : #line 1274
    1284                 :          6 :         .wg_hist = wg_hist,
    1285                 :         28 : #line 1272
    1286                 :            :         .use_histogram = use_histogram,
    1287                 :            :     };
    1288                 :            : #line 1263
    1289                 :            :     size_t _glsl_1263_fn(void *, pl_str *, const uint8_t *);
    1290                 :            :     pl_str_builder_append(sh->buffers[SH_BUF_BODY], _glsl_1263_fn,
    1291                 :            :                           &_glsl_1263_args, sizeof(_glsl_1263_args));
    1292                 :         24 : }
    1293                 :         12 : #line 1277
    1294                 :          4 : 
    1295                 :          6 :     // Decode color into linear light representation
    1296                 :          6 :     pl_color_space_infer(&csp);
    1297                 :          0 :     pl_shader_linearize(sh, &csp);
    1298                 :          6 : 
    1299         [ -  + ]:          6 :     bool has_subgroups = sh_glsl(sh).subgroup_size > 0;
    1300                 :            :     const float cutoff = fmaxf(params->black_cutoff, 0.0f) * 1e-2f;
    1301         [ +  + ]:          6 : {
    1302                 :            :     const struct __attribute__((__packed__)) {
    1303                 :            :     float pl_color_sdr_white_10000_0;
    1304                 :            :     float pq_m1;
    1305                 :            :     float pq_c1;
    1306         [ -  - ]:         14 :     float pq_c2;
    1307                 :            :     float pq_c3;
    1308                 :            :     float pq_m2;
    1309                 :          0 :     float pq_max;
    1310                 :          0 :     int pq_bits_hist_bits;
    1311                 :          0 :     int hist_bias;
    1312                 :            :     int hist_bins_1;
    1313                 :            :     unsigned hist_bins;
    1314                 :          3 :     ident_t sh_luma_coeffs_sh_csp;
    1315                 :            :     ident_t cutoff_;
    1316         [ +  + ]:          6 :     ident_t wg_hist;
    1317                 :          2 :     ident_t wg_sum;
    1318                 :          3 :     ident_t wg_max;
    1319         [ +  + ]:          3 :     ident_t wg_black;
    1320                 :            :     bool cutoff;
    1321                 :            :     bool use_histogram;
    1322                 :          5 :     bool has_subgroups;
    1323                 :          6 : } _glsl_1284_args = {
    1324                 :          2 : #line 1286
    1325                 :          3 :         .pl_color_sdr_white_10000_0 = PL_COLOR_SDR_WHITE / 10000.0,
    1326         [ +  + ]:          3 : #line 1287
    1327                 :            :         .pq_m1 = PQ_M1,
    1328                 :          5 : #line 1288
    1329                 :          3 :         .pq_c1 = PQ_C1,
    1330                 :            : #line 1288
    1331                 :          2 :         .pq_c2 = PQ_C2,
    1332         [ +  + ]:          8 : #line 1289
    1333                 :            :         .pq_c3 = PQ_C3,
    1334                 :            : #line 1290
    1335                 :            :         .pq_m2 = PQ_M2,
    1336         [ -  + ]:          6 : #line 1293
    1337         [ #  # ]:          0 :         .pq_max = PQ_MAX,
    1338                 :            : #line 1297
    1339                 :          0 :         .pq_bits_hist_bits = PQ_BITS - HIST_BITS,
    1340                 :          0 : #line 1298
    1341                 :            :         .hist_bias = HIST_BIAS,
    1342                 :            : #line 1299
    1343                 :            :         .hist_bins_1 = HIST_BINS - 1,
    1344                 :          0 : #line 1340
    1345                 :            :         .hist_bins = HIST_BINS,
    1346                 :            : #line 1285
    1347                 :            :         .sh_luma_coeffs_sh_csp = sh_luma_coeffs(sh, &csp),
    1348                 :            : #line 1292
    1349                 :            :         .cutoff_ = sh_const_float(sh, "cutoff",  cutoff),
    1350                 :            : #line 1304
    1351                 :            :         .wg_hist = wg_hist,
    1352                 :            : #line 1319
    1353                 :            :         .wg_sum = wg_sum,
    1354                 :            : #line 1320
    1355                 :            :         .wg_max = wg_max,
    1356                 :            : #line 1322
    1357                 :         14 :         .wg_black = wg_black,
    1358                 :          6 : #line 1291
    1359                 :          6 :         .cutoff = cutoff,
    1360                 :        791 : #line 1296
    1361                 :            :         .use_histogram = use_histogram,
    1362                 :            : #line 1300
    1363   [ +  +  -  + ]:        791 :         .has_subgroups = has_subgroups,
    1364                 :            :     };
    1365                 :            : #line 1284
    1366                 :        782 :     size_t _glsl_1284_fn(void *, pl_str *, const uint8_t *);
    1367                 :        782 :     pl_str_builder_append(sh->buffers[SH_BUF_BODY], _glsl_1284_fn,
    1368         [ +  + ]:        782 :                           &_glsl_1284_args, sizeof(_glsl_1284_args));
    1369                 :            : }
    1370                 :            : #line 1356
    1371                 :         14 : 
    1372                 :         14 :     return true;
    1373                 :         14 : }
    1374                 :            : 
    1375                 :            : bool pl_get_detected_hdr_metadata(const pl_shader_obj state,
    1376                 :        783 :                                   struct pl_hdr_metadata *out)
    1377                 :            : {
    1378   [ +  +  +  - ]:        783 :     if (!state || state->type != PL_SHADER_OBJ_COLOR_MAP)
    1379                 :            :         return false;
    1380                 :            : 
    1381                 :        774 :     struct sh_color_map_obj *obj = state->priv;
    1382                 :        774 :     update_peak_buf(state->gpu, obj, false);
    1383                 :        774 :     if (!obj->peak.avg_pq)
    1384                 :        774 :         return false;
    1385                 :        774 : 
    1386                 :            :     out->max_pq_y = obj->peak.max_pq;
    1387                 :            :     out->avg_pq_y = obj->peak.avg_pq;
    1388                 :          0 :     return true;
    1389                 :            : }
    1390         [ #  # ]:          0 : 
    1391                 :            : void pl_reset_detected_peak(pl_shader_obj state)
    1392                 :            : {
    1393                 :          0 :     if (!state || state->type != PL_SHADER_OBJ_COLOR_MAP)
    1394                 :          0 :         return;
    1395                 :          0 : 
    1396                 :            :     struct sh_color_map_obj *obj = state->priv;
    1397                 :            :     pl_buf readback = obj->peak.readback;
    1398                 :            :     pl_buf_destroy(state->gpu, &obj->peak.buf);
    1399                 :            :     memset(&obj->peak, 0, sizeof(obj->peak));
    1400                 :            :     obj->peak.readback = readback;
    1401                 :            : }
    1402                 :            : 
    1403                 :            : void pl_shader_extract_features(pl_shader sh, struct pl_color_space csp)
    1404                 :            : {
    1405                 :            :     if (!sh_require(sh, PL_SHADER_SIG_COLOR, 0, 0))
    1406                 :            :         return;
    1407                 :            : 
    1408                 :            :     sh_describe(sh, "feature extraction");
    1409                 :            :     pl_shader_linearize(sh, &csp);
    1410                 :            :     GLSL("// pl_shader_extract_features             \n"
    1411                 :            :          "{                                         \n"
    1412                 :            :          "vec3 lms = %f * "$" * color.rgb;          \n"
    1413                 :            :          "lms = pow(max(lms, 0.0), vec3(%f));       \n"
    1414                 :          8 :          "lms = (vec3(%f) + %f * lms)               \n"
    1415                 :            :          "        / (vec3(1.0) + %f * lms);         \n"
    1416   [ -  +  +  - ]:          8 :          "lms = pow(lms, vec3(%f));                 \n"
    1417                 :            :          "float I = dot(vec3(%f, %f, %f), lms);     \n"
    1418   [ -  +  +  - ]:          8 :          "color = vec4(I, 0.0, 0.0, 1.0);           \n"
    1419                 :            :          "}                                         \n",
    1420                 :            :          PL_COLOR_SDR_WHITE / 10000,
    1421                 :         16 :          SH_MAT3(pl_ipt_rgb2lms(pl_raw_primaries_get(csp.primaries))),
    1422                 :          8 :          PQ_M1, PQ_C1, PQ_C2, PQ_C3, PQ_M2,
    1423                 :          8 :          pl_ipt_lms2ipt.m[0][0], pl_ipt_lms2ipt.m[0][1], pl_ipt_lms2ipt.m[0][2]);
    1424                 :          8 : }
    1425                 :          8 : 
    1426                 :            : const struct pl_color_map_params pl_color_map_default_params = { PL_COLOR_MAP_DEFAULTS };
    1427                 :            : const struct pl_color_map_params pl_color_map_high_quality_params = { PL_COLOR_MAP_HQ_DEFAULTS };
    1428                 :            : 
    1429                 :          4 : static ident_t rect_pos(pl_shader sh, pl_rect2df rc)
    1430                 :            : {
    1431                 :            :     if (!rc.x0 && !rc.x1)
    1432         [ -  + ]:          4 :         rc.x1 = 1.0f;
    1433         [ -  + ]:          4 :     if (!rc.y0 && !rc.y1)
    1434                 :            :         rc.y1 = 1.0f;
    1435                 :          4 : 
    1436                 :            :     return sh_attr_vec2(sh, "tone_map_coords", &(pl_rect2df) {
    1437                 :            :         .x0 = -rc.x0         / (rc.x1 - rc.x0),
    1438                 :            :         .x1 = (1.0f - rc.x0) / (rc.x1 - rc.x0),
    1439                 :            :         .y0 = -rc.y1         / (rc.y0 - rc.y1),
    1440                 :            :         .y1 = (1.0f - rc.y1) / (rc.y0 - rc.y1),
    1441                 :            :     });
    1442                 :            : }
    1443                 :            : 
    1444                 :            : static void visualize_tone_map(pl_shader sh, pl_rect2df rc, float alpha,
    1445                 :            :                                const struct pl_tone_map_params *params)
    1446                 :            : {
    1447                 :            :     pl_assert(params->input_scaling  == PL_HDR_PQ);
    1448                 :            :     pl_assert(params->output_scaling == PL_HDR_PQ);
    1449                 :            : 
    1450                 :            :     GLSL("// Visualize tone mapping                 \n"
    1451                 :            :          "{                                         \n"
    1452                 :            :          "vec2 pos = "$";                           \n"
    1453                 :            :          "if (min(pos.x, pos.y) >= 0.0 &&           \n" // visualizer rect
    1454                 :            :          "    max(pos.x, pos.y) <= 1.0)             \n"
    1455                 :            :          "{                                         \n"
    1456                 :            :          "float xmin = "$";                         \n"
    1457                 :            :          "float xmax = "$";                         \n"
    1458                 :            :          "float xavg = "$";                         \n"
    1459                 :            :          "float ymin = "$";                         \n"
    1460                 :            :          "float ymax = "$";                         \n"
    1461                 :            :          "float alpha = 0.8 * "$";                  \n"
    1462                 :            :          "vec3 viz = color.rgb;                     \n"
    1463                 :            :          "float vv = tone_map(pos.x);               \n"
    1464                 :            :          // Color based on region
    1465                 :            :          "if (pos.x < xmin || pos.x > xmax) {       \n" // outside source
    1466                 :            :          "} else if (pos.y < ymin || pos.y > ymax) {\n" // outside target
    1467                 :            :          "    if (pos.y < xmin || pos.y > xmax) {   \n" //  and also source
    1468                 :            :          "        viz = vec3(0.1, 0.1, 0.5);        \n"
    1469                 :            :          "    } else {                              \n"
    1470                 :            :          "        viz = vec3(0.2, 0.05, 0.05);      \n" //  but inside source
    1471                 :            :          "    }                                     \n"
    1472                 :            :          "} else {                                  \n" // inside domain
    1473                 :            :          "    if (abs(pos.x - pos.y) < 1e-3) {      \n" // main diagonal
    1474                 :            :          "        viz = vec3(0.2);                  \n"
    1475                 :            :          "    } else if (pos.y < vv) {              \n" // inside function
    1476                 :            :          "        alpha *= 0.6;                     \n"
    1477                 :            :          "        viz = vec3(0.05);                 \n"
    1478                 :            :          "        if (vv > pos.x && pos.y > pos.x)  \n" // output brighter than input
    1479                 :            :          "            viz.rg = vec2(0.5, 0.7);      \n"
    1480                 :            :          "    } else {                              \n" // outside function
    1481                 :            :          "        if (vv < pos.x && pos.y < pos.x)  \n" // output darker than input
    1482                 :            :          "            viz = vec3(0.0, 0.1, 0.2);    \n"
    1483                 :            :          "    }                                     \n"
    1484                 :            :          "    if (pos.y > xmax) {                   \n" // inverse tone-mapping region
    1485                 :            :          "        vec3 hi = vec3(0.2, 0.5, 0.8);    \n"
    1486                 :            :          "        viz = mix(viz, hi, 0.5);          \n"
    1487                 :            :          "    } else if (pos.y < xmin) {            \n" // black point region
    1488                 :          4 :          "        viz = mix(viz, vec3(0.0), 0.3);   \n"
    1489                 :            :          "    }                                     \n"
    1490                 :          4 :          "    if (xavg > 0.0 && abs(pos.x - xavg) < 1e-3)\n" // source avg brightness
    1491                 :            :          "        viz = vec3(0.5);                  \n"
    1492                 :            :          "}                                         \n"
    1493                 :            :          "color.rgb = mix(color.rgb, viz, alpha);   \n"
    1494                 :          4 :          "}                                         \n"
    1495                 :          4 :          "}                                         \n",
    1496                 :          4 :          rect_pos(sh, rc),
    1497                 :            :          SH_FLOAT_DYN(params->input_min),
    1498                 :          4 :          SH_FLOAT_DYN(params->input_max),
    1499                 :            :          SH_FLOAT_DYN(params->input_avg),
    1500                 :            :          SH_FLOAT(params->output_min),
    1501                 :            :          SH_FLOAT_DYN(params->output_max),
    1502                 :            :          SH_FLOAT_DYN(alpha));
    1503                 :            : }
    1504                 :            : 
    1505                 :            : static void visualize_gamut_map(pl_shader sh, pl_rect2df rc,
    1506                 :            :                                 ident_t lut, float hue, float theta,
    1507                 :            :                                 const struct pl_gamut_map_params *params)
    1508                 :            : {
    1509                 :            :     ident_t ipt2lms = SH_MAT3(pl_ipt_ipt2lms);
    1510                 :            :     ident_t lms2rgb_src = SH_MAT3(pl_ipt_lms2rgb(&params->input_gamut));
    1511                 :            :     ident_t lms2rgb_dst = SH_MAT3(pl_ipt_lms2rgb(&params->output_gamut));
    1512                 :            : 
    1513                 :            :     GLSL("// Visualize gamut mapping                            \n"
    1514                 :            :          "vec2 pos = "$";                                       \n"
    1515                 :            :          "float pqmin = "$";                                    \n"
    1516                 :            :          "float pqmax = "$";                                    \n"
    1517                 :            :          "float rgbmin = "$";                                   \n"
    1518                 :            :          "float rgbmax = "$";                                   \n"
    1519                 :            :          "vec3 orig = ipt;                                      \n"
    1520                 :            :          "if (min(pos.x, pos.y) >= 0.0 &&                       \n"
    1521                 :            :          "    max(pos.x, pos.y) <= 1.0)                         \n"
    1522                 :            :          "{                                                     \n"
    1523                 :            :          // Source color to visualize
    1524                 :            :          "float mid = mix(pqmin, pqmax, 0.6);                   \n"
    1525                 :            :          "vec3 base = vec3(0.5, 0.0, 0.0);                      \n"
    1526                 :            :          "float hue = "$", theta = "$";                         \n"
    1527                 :            :          "base.x = mix(base.x, mid, sin(theta));                \n"
    1528                 :            :          "mat3 rot1 = mat3(1.0,    0.0,      0.0,               \n"
    1529                 :            :          "                 0.0,  cos(hue), sin(hue),            \n"
    1530                 :            :          "                 0.0, -sin(hue), cos(hue));           \n"
    1531                 :            :          "mat3 rot2 = mat3( cos(theta), 0.0, sin(theta),        \n"
    1532                 :            :          "                     0.0,     1.0,    0.0,            \n"
    1533                 :            :          "                 -sin(theta), 0.0, cos(theta));       \n"
    1534                 :            :          "vec3 dir = vec3(pos.yx - vec2(0.5), 0.0);             \n"
    1535                 :            :          "ipt = base + rot1 * rot2 * dir;                       \n"
    1536                 :            :          // Convert back to RGB (for gamut boundary testing)
    1537                 :            :          "lmspq = "$" * ipt;                                    \n"
    1538                 :            :          "lms = pow(max(lmspq, 0.0), vec3(1.0/%f));             \n"
    1539                 :            :          "lms = max(lms - vec3(%f), 0.0)                        \n"
    1540                 :            :          "             / (vec3(%f) - %f * lms);                 \n"
    1541                 :            :          "lms = pow(lms, vec3(1.0/%f));                         \n"
    1542                 :            :          "lms *= %f;                                            \n"
    1543                 :            :          // Check against src/dst gamut boundaries
    1544                 :            :          "vec3 rgbsrc = "$" * lms;                              \n"
    1545                 :            :          "vec3 rgbdst = "$" * lms;                              \n"
    1546                 :            :          "bool insrc, indst;                                    \n"
    1547                 :            :          "insrc = all(lessThan(rgbsrc, vec3(rgbmax))) &&        \n"
    1548                 :            :          "              all(greaterThan(rgbsrc, vec3(rgbmin))); \n"
    1549                 :            :          "indst = all(lessThan(rgbdst, vec3(rgbmax))) &&        \n"
    1550                 :            :          "              all(greaterThan(rgbdst, vec3(rgbmin))); \n"
    1551                 :            :          // Sample from gamut mapping 3DLUT
    1552                 :            :          "idx.x = (ipt.x - pqmin) / (pqmax - pqmin);            \n"
    1553                 :            :          "idx.y = 2.0 * length(ipt.yz);                         \n"
    1554                 :            :          "idx.z = %f * atan(ipt.z, ipt.y) + 0.5;                \n"
    1555                 :            :          "vec3 mapped = "$"(idx).xyz;                           \n"
    1556                 :            :          "mapped.yz -= vec2(32768.0/65535.0);                   \n"
    1557                 :            :          "float mappedhue = atan(mapped.z, mapped.y);           \n"
    1558                 :            :          "float mappedchroma = length(mapped.yz);               \n"
    1559                 :            :          "ipt = mapped;                                         \n"
    1560                 :            :          // Visualize gamuts
    1561                 :            :          "if (!insrc && !indst) {                               \n"
    1562                 :            :          "    ipt = orig;                                       \n"
    1563                 :            :          "} else if (insrc && !indst) {                         \n"
    1564                 :            :          "    ipt.x -= 0.1;                                     \n"
    1565                 :            :          "} else if (indst && !insrc) {                         \n"
    1566                 :            :          "    ipt.x += 0.1;                                     \n"
    1567                 :            :          "}                                                     \n"
    1568                 :            :          // Visualize iso-luminance and iso-hue lines
    1569                 :            :          "vec3 line;                                            \n"
    1570                 :            :          "if (insrc && fract(50.0 * mapped.x) < 1e-1) {         \n"
    1571                 :            :          "    float k = smoothstep(0.1, 0.0, abs(sin(theta)));  \n"
    1572                 :            :          "    line.x = mix(mapped.x, 0.3, 0.5);                 \n"
    1573                 :            :          "    line.yz = sqrt(length(mapped.yz)) *               \n"
    1574                 :            :          "              normalize(mapped.yz);                   \n"
    1575                 :            :          "    ipt = mix(ipt, line, k);                          \n"
    1576                 :            :          "}                                                     \n"
    1577                 :            :          "if (insrc && fract(10.0 * (mappedhue - hue)) < 1e-1) {\n"
    1578                 :            :          "    float k = smoothstep(0.3, 0.0, abs(cos(theta)));  \n"
    1579                 :            :          "    line.x = mapped.x - 0.05;                         \n"
    1580                 :            :          "    line.yz = 1.2 * mapped.yz;                        \n"
    1581                 :            :          "    ipt = mix(ipt, line, k);                          \n"
    1582                 :            :          "}                                                     \n"
    1583                 :            :          "if (insrc && fract(100.0 * mappedchroma) < 1e-1) {    \n"
    1584                 :            :          "    line.x = mapped.x + 0.1;                          \n"
    1585                 :            :          "    line.yz = 0.4 * mapped.yz;                        \n"
    1586                 :          4 :          "    ipt = mix(ipt, line, 0.5);                        \n"
    1587                 :            :          "}                                                     \n"
    1588                 :         10 :          "}                                                     \n",
    1589                 :            :          rect_pos(sh, rc),
    1590                 :         10 :          SH_FLOAT(params->min_luma), SH_FLOAT(params->max_luma),
    1591                 :         10 :          SH_FLOAT(pl_hdr_rescale(PL_HDR_PQ, PL_HDR_NORM, params->min_luma)),
    1592                 :         10 :          SH_FLOAT(pl_hdr_rescale(PL_HDR_PQ, PL_HDR_NORM, params->max_luma)),
    1593                 :            :          SH_FLOAT_DYN(hue), SH_FLOAT_DYN(theta),
    1594                 :         16 :          ipt2lms,
    1595                 :            :          PQ_M2, PQ_C1, PQ_C2, PQ_C3, PQ_M1,
    1596                 :         16 :          10000 / PL_COLOR_SDR_WHITE,
    1597                 :         16 :          lms2rgb_src,
    1598                 :         16 :          lms2rgb_dst,
    1599                 :         16 :          0.5f / M_PI,
    1600                 :            :          lut);
    1601                 :            : }
    1602                 :            : 
    1603                 :            : static void fill_tone_lut(void *data, const struct sh_lut_params *params)
    1604         [ -  + ]:         16 : {
    1605         [ -  + ]:         16 :     const struct pl_tone_map_params *lut_params = params->priv;
    1606         [ +  + ]:    6291472 :     pl_tone_map_generate(data, lut_params);
    1607                 :    6291456 : }
    1608                 :    6291456 : 
    1609                 :    6291456 : static void fill_gamut_lut(void *data, const struct sh_lut_params *params)
    1610                 :    6291456 : {
    1611                 :    6291456 :     const struct pl_gamut_map_params *lut_params = params->priv;
    1612                 :            :     const int lut_size = params->width * params->height * params->depth;
    1613                 :            :     void *tmp = pl_alloc(NULL, lut_size * sizeof(float) * lut_params->lut_stride);
    1614                 :         16 :     pl_gamut_map_generate(tmp, lut_params);
    1615                 :         16 : 
    1616                 :            :     // Convert to 16-bit unsigned integer for GPU texture
    1617                 :       1881 :     const float *in = tmp;
    1618                 :            :     uint16_t *out = data;
    1619                 :            :     pl_assert(lut_params->lut_stride == 3);
    1620         [ +  - ]:       1881 :     pl_assert(params->comps == 4);
    1621                 :       1823 :     for (int i = 0; i < lut_size; i++) {
    1622                 :            :         out[0] = roundf(in[0] * UINT16_MAX);
    1623                 :       1881 :         out[1] = roundf(in[1] * UINT16_MAX + (UINT16_MAX >> 1));
    1624                 :            :         out[2] = roundf(in[2] * UINT16_MAX + (UINT16_MAX >> 1));
    1625         [ +  + ]:       1881 :         in  += 3;
    1626                 :        789 :         out += 4;
    1627                 :        789 :     }
    1628                 :            : 
    1629         [ +  - ]:        789 :     pl_free(tmp);
    1630                 :            : }
    1631                 :            : 
    1632                 :            : void pl_shader_color_map_ex(pl_shader sh, const struct pl_color_map_params *params,
    1633                 :       1881 :                             const struct pl_color_map_args *args)
    1634         [ +  + ]:       1881 : {
    1635         [ +  + ]:       1823 :     if (!sh_require(sh, PL_SHADER_SIG_COLOR, 0, 0))
    1636                 :        208 :         return;
    1637                 :       1823 : 
    1638                 :            :     struct pl_color_space src = args->src, dst = args->dst;
    1639                 :            :     struct sh_color_map_obj *obj = NULL;
    1640         [ -  + ]:         58 :     if (args->state) {
    1641                 :         58 :         pl_get_detected_hdr_metadata(*args->state, &src.hdr);
    1642                 :            :         obj = SH_OBJ(sh, args->state, PL_SHADER_OBJ_COLOR_MAP, struct sh_color_map_obj,
    1643                 :            :                      sh_color_map_uninit);
    1644                 :        174 :         if (!obj)
    1645         [ +  - ]:         58 :             return;
    1646                 :            :     }
    1647                 :         58 : 
    1648                 :            :     pl_color_space_infer_map(&src, &dst);
    1649                 :            :     if (pl_color_space_equal(&src, &dst)) {
    1650         [ +  - ]:         58 :         if (args->prelinearized)
    1651                 :            :             pl_shader_delinearize(sh, &dst);
    1652                 :            :         return;
    1653                 :            :     }
    1654                 :         58 : 
    1655                 :            :     params = PL_DEF(params, &pl_color_map_default_params);
    1656                 :            :     GLSL("// pl_shader_color_map \n"
    1657                 :            :          "{                      \n");
    1658                 :            : 
    1659                 :            :     struct pl_tone_map_params tone = {
    1660                 :            :         .function       = PL_DEF(params->tone_mapping_function, &pl_tone_map_clip),
    1661                 :            :         .constants      = params->tone_constants,
    1662                 :            :         .param          = params->tone_mapping_param,
    1663                 :         58 :         .input_scaling  = PL_HDR_PQ,
    1664                 :            :         .output_scaling = PL_HDR_PQ,
    1665                 :            :         .lut_size       = PL_DEF(params->lut_size, pl_color_map_default_params.lut_size),
    1666                 :            :         .hdr            = src.hdr,
    1667                 :            :     };
    1668                 :            : 
    1669                 :            :     pl_color_space_nominal_luma_ex(pl_nominal_luma_params(
    1670                 :            :         .color      = &src,
    1671                 :         58 :         .metadata   = params->metadata,
    1672                 :            :         .scaling    = tone.input_scaling,
    1673                 :            :         .out_min    = &tone.input_min,
    1674         [ +  + ]:         58 :         .out_max    = &tone.input_max,
    1675                 :         36 :         .out_avg    = &tone.input_avg,
    1676         [ +  + ]:         58 :     ));
    1677                 :         36 : 
    1678                 :            :     pl_color_space_nominal_luma_ex(pl_nominal_luma_params(
    1679         [ +  + ]:         58 :         .color      = &dst,
    1680                 :            :         .metadata   = PL_HDR_METADATA_HDR10,
    1681                 :            :         .scaling    = tone.output_scaling,
    1682         [ +  + ]:         92 :         .out_min    = &tone.output_min,
    1683                 :            :         .out_max    = &tone.output_max,
    1684                 :            :     ));
    1685                 :            : 
    1686                 :        290 :     pl_tone_map_params_infer(&tone);
    1687         [ +  - ]:         58 : 
    1688                 :            :     // Round sufficiently similar values
    1689                 :            :     if (fabs(tone.input_max - tone.output_max) < 1e-6)
    1690                 :            :         tone.output_max = tone.input_max;
    1691         [ +  - ]:         58 :     if (fabs(tone.input_min - tone.output_min) < 1e-6)
    1692         [ +  - ]:         58 :         tone.output_min = tone.input_min;
    1693         [ +  - ]:         58 : 
    1694                 :            :     if (!params->inverse_tone_mapping) {
    1695                 :            :         // Never exceed the source unless requested, but still allow
    1696                 :            :         // black point adaptation
    1697                 :            :         tone.output_max = PL_MIN(tone.output_max, tone.input_max);
    1698                 :         58 :     }
    1699                 :            : 
    1700                 :            :     const int *lut3d_size_def = pl_color_map_default_params.lut3d_size;
    1701                 :            :     struct pl_gamut_map_params gamut = {
    1702                 :            :         .function        = PL_DEF(params->gamut_mapping, &pl_gamut_map_clip),
    1703                 :            :         .constants       = params->gamut_constants,
    1704                 :            :         .input_gamut     = src.hdr.prim,
    1705                 :         58 :         .output_gamut    = dst.hdr.prim,
    1706                 :            :         .lut_size_I      = PL_DEF(params->lut3d_size[0], lut3d_size_def[0]),
    1707                 :            :         .lut_size_C      = PL_DEF(params->lut3d_size[1], lut3d_size_def[1]),
    1708                 :            :         .lut_size_h      = PL_DEF(params->lut3d_size[2], lut3d_size_def[2]),
    1709                 :            :         .lut_stride      = 3,
    1710                 :            :     };
    1711                 :            : 
    1712                 :            :     float src_peak_static;
    1713                 :            :     pl_color_space_nominal_luma_ex(pl_nominal_luma_params(
    1714   [ +  -  +  - ]:         58 :         .color      = &src,
    1715         [ +  - ]:         58 :         .metadata   = PL_HDR_METADATA_HDR10,
    1716                 :         58 :         .scaling    = PL_HDR_PQ,
    1717                 :            :         .out_max    = &src_peak_static,
    1718                 :            :     ));
    1719                 :            : 
    1720                 :            :     pl_color_space_nominal_luma_ex(pl_nominal_luma_params(
    1721                 :            :         .color      = &dst,
    1722   [ +  -  -  -  :         58 :         .metadata   = PL_HDR_METADATA_HDR10,
                   -  - ]
    1723                 :         58 :         .scaling    = PL_HDR_PQ,
    1724      [ -  -  + ]:         58 :         .out_min    = &gamut.min_luma,
    1725                 :            :         .out_max    = &gamut.max_luma,
    1726                 :            :     ));
    1727                 :            : 
    1728                 :            :     // Clip the gamut mapping output to the input gamut if disabled
    1729                 :          0 :     if (!params->gamut_expansion && gamut.function->bidirectional) {
    1730                 :          0 :         if (pl_primaries_compatible(&gamut.input_gamut, &gamut.output_gamut)) {
    1731                 :          0 :             gamut.output_gamut = pl_primaries_clip(&gamut.output_gamut,
    1732                 :          0 :                                                    &gamut.input_gamut);
    1733                 :          0 :         }
    1734                 :          0 :     }
    1735                 :            : 
    1736                 :            :     // Backwards compatibility with older API
    1737                 :          0 :     switch (params->gamut_mode) {
    1738                 :          0 :     case PL_GAMUT_CLIP:
    1739                 :          0 :         switch (params->intent) {
    1740                 :          0 :         case PL_INTENT_AUTO:
    1741                 :          0 :         case PL_INTENT_PERCEPTUAL:
    1742                 :          0 :         case PL_INTENT_RELATIVE_COLORIMETRIC:
    1743                 :          0 :             break; // leave default
    1744                 :          0 :         case PL_INTENT_SATURATION:
    1745                 :          0 :             gamut.function = &pl_gamut_map_saturation;
    1746                 :            :             break;
    1747                 :          0 :         case PL_INTENT_ABSOLUTE_COLORIMETRIC:
    1748                 :            :             gamut.function = &pl_gamut_map_absolute;
    1749                 :            :             break;
    1750                 :         58 :         }
    1751         [ +  + ]:         58 :         break;
    1752                 :            :     case PL_GAMUT_DARKEN:
    1753                 :            :         gamut.function = &pl_gamut_map_darken;
    1754         [ +  - ]:         24 :         break;
    1755                 :         24 :     case PL_GAMUT_WARN:
    1756         [ -  + ]:         24 :         gamut.function = &pl_gamut_map_highlight;
    1757                 :         24 :         break;
    1758                 :            :     case PL_GAMUT_DESATURATE:
    1759                 :            :         gamut.function = &pl_gamut_map_desaturate;
    1760                 :         58 :         break;
    1761         [ -  + ]:         58 :     case PL_GAMUT_MODE_COUNT:
    1762                 :          0 :         pl_unreachable();
    1763                 :            :     }
    1764                 :            : 
    1765                 :            :     bool can_fast = !params->force_tone_mapping_lut;
    1766                 :         58 :     if (!args->state) {
    1767                 :         58 :         // No state object provided, forcibly disable advanced methods
    1768                 :            :         can_fast = true;
    1769         [ +  + ]:         58 :         if (tone.function != &pl_tone_map_clip)
    1770                 :         42 :             tone.function = &pl_tone_map_linear;
    1771                 :            :         if (gamut.function != &pl_gamut_map_clip)
    1772                 :         58 :             gamut.function = &pl_gamut_map_saturation;
    1773                 :         58 :     }
    1774                 :         58 : 
    1775                 :         58 :     pl_fmt gamut_fmt = pl_find_fmt(SH_GPU(sh), PL_FMT_UNORM, 4, 16, 16, PL_FMT_CAP_LINEAR);
    1776                 :            :     if (!gamut_fmt) {
    1777   [ +  +  -  +  :         58 :         gamut.function = &pl_gamut_map_saturation;
                   -  - ]
    1778                 :          0 :         can_fast = true;
    1779                 :          0 :     }
    1780                 :          0 : 
    1781                 :          0 :     bool need_tone_map = !pl_tone_map_params_noop(&tone);
    1782                 :          0 :     bool need_gamut_map = !pl_gamut_map_params_noop(&gamut);
    1783                 :            : 
    1784                 :            :     if (!args->prelinearized)
    1785                 :            :         pl_shader_linearize(sh, &src);
    1786                 :            : 
    1787         [ +  + ]:         58 :     pl_matrix3x3 rgb2lms = pl_ipt_rgb2lms(pl_raw_primaries_get(src.primaries));
    1788         [ -  + ]:         28 :     pl_matrix3x3 lms2rgb = pl_ipt_lms2rgb(pl_raw_primaries_get(dst.primaries));
    1789                 :          0 :     ident_t lms2ipt = SH_MAT3(pl_ipt_lms2ipt);
    1790                 :          0 :     ident_t ipt2lms = SH_MAT3(pl_ipt_ipt2lms);
    1791                 :          0 : 
    1792                 :            :     if (need_gamut_map && gamut.function == &pl_gamut_map_saturation && can_fast) {
    1793                 :         28 :         const pl_matrix3x3 lms2src = pl_ipt_lms2rgb(&gamut.input_gamut);
    1794                 :            :         const pl_matrix3x3 dst2lms = pl_ipt_rgb2lms(&gamut.output_gamut);
    1795                 :            :         sh_describe(sh, "gamut map (saturation)");
    1796                 :            :         pl_matrix3x3_mul(&lms2rgb, &dst2lms);
    1797                 :         30 :         pl_matrix3x3_mul(&lms2rgb, &lms2src);
    1798                 :            :         need_gamut_map = false;
    1799                 :            :     }
    1800                 :            : 
    1801                 :            :     // Fast path: simply convert between primaries (if needed)
    1802                 :            :     if (!need_tone_map && !need_gamut_map) {
    1803                 :            :         if (src.primaries != dst.primaries) {
    1804                 :            :             sh_describe(sh, "colorspace conversion");
    1805                 :            :             pl_matrix3x3_mul(&lms2rgb, &rgb2lms);
    1806                 :            :             GLSL("color.rgb = "$" * color.rgb; \n", SH_MAT3(lms2rgb));
    1807                 :            :         }
    1808                 :            :         goto done;
    1809                 :            :     }
    1810         [ -  + ]:         30 : 
    1811                 :            :     // Full path: convert input from normalized RGB to IPT
    1812                 :          0 :     GLSL("vec3 lms = "$" * color.rgb;               \n"
    1813                 :            :          "vec3 lmspq = %f * lms;                    \n"
    1814                 :            :          "lmspq = pow(max(lmspq, 0.0), vec3(%f));   \n"
    1815                 :            :          "lmspq = (vec3(%f) + %f * lmspq)           \n"
    1816                 :            :          "        / (vec3(1.0) + %f * lmspq);       \n"
    1817                 :            :          "lmspq = pow(lmspq, vec3(%f));             \n"
    1818                 :            :          "vec3 ipt = "$" * lmspq;                   \n"
    1819                 :            :          "float i_orig = ipt.x;                     \n",
    1820                 :            :          SH_MAT3(rgb2lms),
    1821                 :            :          PL_COLOR_SDR_WHITE / 10000,
    1822                 :            :          PQ_M1, PQ_C1, PQ_C2, PQ_C3, PQ_M2,
    1823         [ +  + ]:         30 :          lms2ipt);
    1824                 :         22 : 
    1825                 :         22 :     if (params->show_clipping) {
    1826                 :         22 :         const float eps = 1e-6f;
    1827                 :         22 :         GLSL("bool clip_hi, clip_lo;                            \n"
    1828                 :            :              "clip_hi = any(greaterThan(color.rgb, vec3("$"))); \n"
    1829   [ -  +  -  - ]:         22 :              "clip_lo = any(lessThan(color.rgb, vec3("$")));    \n"
    1830                 :            :              "clip_hi = clip_hi || ipt.x > "$";                 \n"
    1831                 :          0 :              "clip_lo = clip_lo || ipt.x < "$";                 \n",
    1832                 :            :              SH_FLOAT_DYN(pl_hdr_rescale(PL_HDR_PQ, PL_HDR_NORM, tone.input_max) + eps),
    1833                 :            :              SH_FLOAT(pl_hdr_rescale(PL_HDR_PQ, PL_HDR_NORM, tone.input_min) - eps),
    1834                 :            :              SH_FLOAT_DYN(tone.input_max + eps),
    1835   [ -  +  -  - ]:         22 :              SH_FLOAT(tone.input_min - eps));
    1836                 :            :     }
    1837                 :          0 : 
    1838                 :          0 :     if (need_tone_map) {
    1839                 :            :         const struct pl_tone_map_function *fun = tone.function;
    1840                 :          0 :         sh_describef(sh, "%s tone map (%.0f -> %.0f)", fun->name,
    1841                 :          0 :                      pl_hdr_rescale(PL_HDR_PQ, PL_HDR_NITS, tone.input_max),
    1842                 :            :                      pl_hdr_rescale(PL_HDR_PQ, PL_HDR_NITS, tone.output_max));
    1843                 :            : 
    1844                 :            :         if (fun == &pl_tone_map_clip && can_fast) {
    1845                 :            : 
    1846                 :            :             GLSL("#define tone_map(x) clamp((x), "$", "$") \n",
    1847                 :            :                  SH_FLOAT(tone.input_min),
    1848                 :            :                  SH_FLOAT_DYN(tone.input_max));
    1849                 :            : 
    1850                 :            :         } else if (fun == &pl_tone_map_linear && can_fast) {
    1851                 :            : 
    1852                 :            :             const float gain = tone.constants.exposure;
    1853                 :            :             const float scale = tone.input_max - tone.input_min;
    1854                 :          0 : 
    1855                 :            :             ident_t linfun = sh_fresh(sh, "linear_pq");
    1856                 :            :             GLSLH("float "$"(float x) {                         \n"
    1857                 :            :                  // Stretch the input range (while clipping)
    1858         [ -  + ]:         22 :                  "    x = "$" * x + "$";                        \n"
    1859                 :         22 :                  "    x = clamp(x, 0.0, 1.0);                   \n"
    1860                 :            :                  "    x = "$" * x + "$";                        \n"
    1861                 :            :                  "    return x;                                 \n"
    1862                 :            :                  "}                                             \n",
    1863                 :            :                  linfun,
    1864                 :            :                  SH_FLOAT_DYN(gain / scale),
    1865                 :            :                  SH_FLOAT_DYN(-gain / scale * tone.input_min),
    1866                 :            :                  SH_FLOAT_DYN(tone.output_max - tone.output_min),
    1867                 :            :                  SH_FLOAT(tone.output_min));
    1868                 :            : 
    1869                 :            :             GLSL("#define tone_map(x) ("$"(x)) \n", linfun);
    1870                 :            : 
    1871                 :         22 :         } else {
    1872         [ -  + ]:         22 : 
    1873                 :          0 :             pl_assert(obj);
    1874                 :          0 :             ident_t lut = sh_lut(sh, sh_lut_params(
    1875                 :            :                 .object     = &obj->tone.lut,
    1876                 :            :                 .var_type   = PL_VAR_FLOAT,
    1877                 :         22 :                 .lut_type   = SH_LUT_AUTO,
    1878                 :         22 :                 .method     = SH_LUT_LINEAR,
    1879                 :            :                 .width      = tone.lut_size,
    1880                 :            :                 .comps      = 1,
    1881                 :            :                 .update     = !pl_tone_map_params_equal(&tone, &obj->tone.params),
    1882                 :            :                 .dynamic    = tone.input_avg > 0, // dynamic metadata
    1883                 :            :                 .fill       = fill_tone_lut,
    1884                 :         22 :                 .priv       = &tone,
    1885   [ +  +  -  +  :         22 :             ));
                   -  - ]
    1886                 :            :             obj->tone.params = tone;
    1887                 :          0 :             if (!lut) {
    1888                 :            :                 SH_FAIL(sh, "Failed generating tone-mapping LUT!");
    1889                 :            :                 return;
    1890                 :            :             }
    1891                 :            : 
    1892                 :          0 :             const float lut_range = tone.input_max - tone.input_min;
    1893                 :            :             GLSL("#define tone_map(x) ("$"("$" * (x) + "$")) \n",
    1894                 :            :                  lut, SH_FLOAT_DYN(1.0f / lut_range),
    1895                 :            :                  SH_FLOAT_DYN(-tone.input_min / lut_range));
    1896                 :            : 
    1897                 :            :         }
    1898                 :            : 
    1899                 :            :         bool need_recovery = tone.input_max >= tone.output_max;
    1900                 :            :         if (need_recovery && params->contrast_recovery && args->feature_map) {
    1901                 :            :             ident_t pos, pt;
    1902                 :            :             ident_t lowres = sh_bind(sh, args->feature_map, PL_TEX_ADDRESS_CLAMP,
    1903                 :            :                                      PL_TEX_SAMPLE_LINEAR, "feature_map",
    1904                 :            :                                      NULL, &pos, &pt);
    1905                 :            : 
    1906                 :            :             // Obtain HF detail map from bicubic interpolation of LF features
    1907                 :            :             GLSL("vec2 lpos  = "$";                                 \n"
    1908                 :            :                  "vec2 lpt   = "$";                                 \n"
    1909                 :            :                  "vec2 lsize = vec2(textureSize("$", 0));           \n"
    1910                 :            :                  "vec2 frac  = fract(lpos * lsize + vec2(0.5));     \n"
    1911                 :            :                  "vec2 frac2 = frac * frac;                         \n"
    1912                 :            :                  "vec2 inv   = vec2(1.0) - frac;                    \n"
    1913                 :            :                  "vec2 inv2  = inv * inv;                           \n"
    1914                 :            :                  "vec2 w0 = 1.0/6.0 * inv2 * inv;                   \n"
    1915                 :            :                  "vec2 w1 = 2.0/3.0 - 0.5 * frac2 * (2.0 - frac);   \n"
    1916                 :            :                  "vec2 w2 = 2.0/3.0 - 0.5 * inv2  * (2.0 - inv);    \n"
    1917                 :            :                  "vec2 w3 = 1.0/6.0 * frac2 * frac;                 \n"
    1918                 :            :                  "vec4 g = vec4(w0 + w1, w2 + w3);                  \n"
    1919                 :            :                  "vec4 h = vec4(w1, w3) / g + inv.xyxy;             \n"
    1920                 :            :                  "h.xy -= vec2(2.0);                                \n"
    1921                 :            :                  "vec4 p = lpos.xyxy + lpt.xyxy * h;                \n"
    1922                 :            :                  "float l00 = textureLod("$", p.xy, 0.0).r;         \n"
    1923                 :            :                  "float l01 = textureLod("$", p.xw, 0.0).r;         \n"
    1924                 :            :                  "float l0 = mix(l01, l00, g.y);                    \n"
    1925                 :            :                  "float l10 = textureLod("$", p.zy, 0.0).r;         \n"
    1926                 :            :                  "float l11 = textureLod("$", p.zw, 0.0).r;         \n"
    1927                 :            :                  "float l1 = mix(l11, l10, g.y);                    \n"
    1928                 :            :                  "float luma = mix(l1, l0, g.x);                    \n"
    1929                 :         22 :                  // Mix low-resolution tone mapped image with high-resolution
    1930                 :            :                  // tone mapped image according to desired strength.
    1931                 :            :                  "float highres = clamp(ipt.x, 0.0, 1.0);           \n"
    1932                 :            :                  "float lowres = clamp(luma, 0.0, 1.0);             \n"
    1933                 :            :                  "float detail = highres - lowres;                  \n"
    1934                 :            :                  "float base = tone_map(highres);                   \n"
    1935                 :         22 :                  "float sharp = tone_map(lowres) + detail;          \n"
    1936                 :            :                  "ipt.x = clamp(mix(base, sharp, "$"), "$", "$");   \n",
    1937                 :            :                  pos, pt, lowres,
    1938                 :            :                  lowres, lowres, lowres, lowres,
    1939                 :            :                  SH_FLOAT(params->contrast_recovery),
    1940         [ +  - ]:         30 :                  SH_FLOAT(tone.output_min), SH_FLOAT_DYN(tone.output_max));
    1941                 :         30 : 
    1942                 :         30 :         } else {
    1943                 :            : 
    1944         [ -  + ]:         30 :             GLSL("ipt.x = tone_map(ipt.x); \n");
    1945         [ +  - ]:         60 :         }
    1946                 :            : 
    1947                 :            :         // Avoid raising saturation excessively when raising brightness, and
    1948                 :            :         // also desaturate when reducing brightness greatly to account for the
    1949                 :            :         // reduction in gamut volume.
    1950                 :            :         GLSL("vec2 hull = vec2(i_orig, ipt.x);                  \n"
    1951                 :            :              "hull = ((hull - 6.0) * hull + 9.0) * hull;        \n"
    1952                 :            :              "ipt.yz *= min(i_orig / ipt.x, hull.y / hull.x);   \n");
    1953                 :            :     }
    1954                 :            : 
    1955                 :            :     if (need_gamut_map) {
    1956                 :            :         const struct pl_gamut_map_function *fun = gamut.function;
    1957                 :            :         sh_describef(sh, "gamut map (%s)", fun->name);
    1958                 :            : 
    1959                 :            :         pl_assert(obj);
    1960         [ -  + ]:         30 :         ident_t lut = sh_lut(sh, sh_lut_params(
    1961                 :          0 :             .object     = &obj->gamut.lut,
    1962                 :          0 :             .var_type   = PL_VAR_FLOAT,
    1963                 :            :             .lut_type   = SH_LUT_TEXTURE,
    1964                 :            :             .fmt        = gamut_fmt,
    1965                 :            :             .method     = params->lut3d_tricubic ? SH_LUT_CUBIC : SH_LUT_LINEAR,
    1966                 :         30 :             .width      = gamut.lut_size_I,
    1967                 :         30 :             .height     = gamut.lut_size_C,
    1968                 :            :             .depth      = gamut.lut_size_h,
    1969                 :            :             .comps      = 4,
    1970                 :            :             .signature  = gamut_map_signature(&gamut),
    1971                 :            :             .cache      = SH_CACHE(sh),
    1972                 :            :             .fill       = fill_gamut_lut,
    1973                 :            :             .priv       = &gamut,
    1974                 :            :         ));
    1975                 :            :         if (!lut) {
    1976                 :            :             SH_FAIL(sh, "Failed generating gamut-mapping LUT!");
    1977         [ -  + ]:         30 :             return;
    1978                 :          0 :         }
    1979                 :            : 
    1980                 :            :         // 3D LUT lookup (in ICh space)
    1981                 :            :         const float lut_range = gamut.max_luma - gamut.min_luma;
    1982         [ +  + ]:         30 :         GLSL("vec3 idx;                             \n"
    1983                 :          4 :              "idx.x = "$" * ipt.x + "$";            \n"
    1984                 :          4 :              "idx.y = 2.0 * length(ipt.yz);         \n"
    1985                 :            :              "idx.z = %f * atan(ipt.z, ipt.y) + 0.5;\n"
    1986                 :            :              "ipt = "$"(idx).xyz;                   \n"
    1987                 :            :              "ipt.yz -= vec2(32768.0/65535.0);      \n",
    1988                 :            :              SH_FLOAT(1.0f / lut_range),
    1989                 :            :              SH_FLOAT(-gamut.min_luma / lut_range),
    1990                 :         30 :              0.5f / M_PI, lut);
    1991                 :            : 
    1992                 :            :         if (params->show_clipping) {
    1993                 :            :             GLSL("clip_lo = clip_lo || any(lessThan(idx, vec3(0.0)));    \n"
    1994                 :            :                  "clip_hi = clip_hi || any(greaterThan(idx, vec3(1.0))); \n");
    1995                 :            :         }
    1996                 :            : 
    1997                 :            :         if (params->visualize_lut) {
    1998                 :            :             visualize_gamut_map(sh, params->visualize_rect, lut,
    1999                 :            :                                 params->visualize_hue, params->visualize_theta,
    2000                 :            :                                 &gamut);
    2001                 :            :         }
    2002         [ -  + ]:         30 :     }
    2003                 :          0 : 
    2004                 :            :     // Convert IPT back to linear RGB
    2005                 :            :     GLSL("lmspq = "$" * ipt;                        \n"
    2006                 :            :          "lms = pow(max(lmspq, 0.0), vec3(1.0/%f)); \n"
    2007                 :            :          "lms = max(lms - vec3(%f), 0.0)            \n"
    2008                 :            :          "             / (vec3(%f) - %f * lms);     \n"
    2009                 :            :          "lms = pow(lms, vec3(1.0/%f));             \n"
    2010                 :            :          "lms *= %f;                                \n"
    2011                 :            :          "color.rgb = "$" * lms;                    \n",
    2012                 :            :          ipt2lms,
    2013                 :            :          PQ_M2, PQ_C1, PQ_C2, PQ_C3, PQ_M1,
    2014                 :            :          10000 / PL_COLOR_SDR_WHITE,
    2015                 :            :          SH_MAT3(lms2rgb));
    2016                 :            : 
    2017                 :            :     if (params->show_clipping) {
    2018         [ +  + ]:         30 :         GLSL("if (clip_hi) {                                                \n"
    2019         [ +  + ]:         22 :              "    float k = dot(color.rgb, vec3(2.0 / 3.0));                \n"
    2020         [ +  - ]:          4 :              "    color.rgb = clamp(vec3(k) - color.rgb, 0.0, 1.0);         \n"
    2021                 :          4 :              "    float cmin = min(min(color.r, color.g), color.b);         \n"
    2022                 :            :              "    float cmax = max(max(color.r, color.g), color.b);         \n"
    2023                 :         22 :              "    float delta = cmax - cmin;                                \n"
    2024                 :            :              "    vec3 sat = smoothstep(cmin - 1e-6, cmax, color.rgb);      \n"
    2025                 :            :              "    const vec3 red = vec3(1.0, 0.0, 0.0);                     \n"
    2026                 :          8 :              "    color.rgb = mix(red, sat, smoothstep(0.0, 0.3, delta));   \n"
    2027                 :         58 :              "} else if (clip_lo) {                                         \n"
    2028                 :         58 :              "    vec3 hi = vec3(0.0, 0.3, 0.3);                            \n"
    2029                 :            :              "    color.rgb = mix(color.rgb, hi, 0.5);                      \n"
    2030                 :            :              "}                                                             \n");
    2031                 :            :     }
    2032                 :          0 : 
    2033                 :            :     if (need_tone_map) {
    2034                 :            :         if (params->visualize_lut) {
    2035                 :            :             float alpha = need_gamut_map ? powf(cosf(params->visualize_theta), 5.0f) : 1.0f;
    2036                 :          0 :             visualize_tone_map(sh, params->visualize_rect, alpha, &tone);
    2037                 :            :         }
    2038                 :            :         GLSL("#undef tone_map \n");
    2039                 :            :     }
    2040                 :            : 
    2041                 :            : done:
    2042                 :            :     pl_shader_delinearize(sh, &dst);
    2043                 :          0 :     GLSL("}\n");
    2044                 :            : }
    2045                 :          4 : 
    2046                 :            : // Backwards compatibility wrapper around `pl_shader_color_map_ex`
    2047                 :            : void pl_shader_color_map(pl_shader sh, const struct pl_color_map_params *params,
    2048         [ +  - ]:          4 :                          struct pl_color_space src, struct pl_color_space dst,
    2049                 :          0 :                          pl_shader_obj *state, bool prelinearized)
    2050   [ +  -  +  - ]:          4 : {
    2051                 :            :     pl_shader_color_map_ex(sh, params, pl_color_map_args(
    2052                 :            :         .src           = src,
    2053                 :          4 :         .dst           = dst,
    2054                 :          4 :         .prelinearized = prelinearized,
    2055                 :          4 :         .state         = state,
    2056                 :            :         .feature_map   = NULL
    2057                 :          4 :     ));
    2058                 :          4 : }
    2059                 :            : 
    2060                 :            : void pl_shader_cone_distort(pl_shader sh, struct pl_color_space csp,
    2061                 :          4 :                             const struct pl_cone_params *params)
    2062                 :          8 : {
    2063                 :            :     if (!sh_require(sh, PL_SHADER_SIG_COLOR, 0, 0))
    2064                 :            :         return;
    2065                 :            :     if (!params || !params->cones)
    2066                 :            :         return;
    2067                 :          4 : 
    2068                 :          4 :     sh_describe(sh, "cone distortion");
    2069                 :            :     GLSL("// pl_shader_cone_distort\n");
    2070                 :            :     GLSL("{\n");
    2071                 :            : 
    2072                 :            :     pl_color_space_infer(&csp);
    2073                 :            :     pl_shader_linearize(sh, &csp);
    2074                 :            : 
    2075                 :            :     pl_matrix3x3 cone_mat;
    2076                 :            :     cone_mat = pl_get_cone_matrix(params, pl_raw_primaries_get(csp.primaries));
    2077                 :            :     GLSL("color.rgb = "$" * color.rgb; \n", sh_var(sh, (struct pl_shader_var) {
    2078                 :            :         .var = pl_var_mat3("cone_mat"),
    2079                 :            :         .data = PL_TRANSPOSE_3X3(cone_mat.m),
    2080                 :            :     }));
    2081                 :            : 
    2082                 :            :     pl_shader_delinearize(sh, &csp);
    2083                 :            :     GLSL("}\n");
    2084                 :            : }
    2085                 :            : 
    2086                 :            : // Auto-generated template functions:
    2087                 :            : #line 1262
    2088                 :            : size_t _glsl_1263_fn(void *alloc, pl_str *buf, const uint8_t *ptr);
    2089                 :            : size_t _glsl_1263_fn(void *alloc, pl_str *buf, const uint8_t *ptr)
    2090                 :            : {
    2091                 :            : struct __attribute__((__packed__)) {
    2092                 :            :     unsigned slices;
    2093                 :            :     unsigned hist_bins;
    2094                 :            :     ident_t wg_sum;
    2095                 :            :     ident_t wg_max;
    2096                 :            :     ident_t wg_black;
    2097                 :            :     ident_t wg_hist;
    2098                 :            :     bool use_histogram;
    2099                 :            : } vars;
    2100                 :            : memcpy(&vars, ptr, sizeof(vars));
    2101                 :            : 
    2102                 :            : #line 1263
    2103                 :            :     pl_str_append_asprintf_c(alloc, buf,
    2104                 :            :         "\n"
    2105                 :            :         "{\n"
    2106                 :            :         "const uint wg_size = gl_WorkGroupSize.x * gl_WorkGroupSize.y;\n"
    2107                 :            :         "const uint wg_idx = gl_WorkGroupID.y * gl_NumWorkGroups.x + gl_WorkGroupID.x;\n"
    2108                 :            :         "const uint local_idx = gl_LocalInvocationIndex;\n"
    2109                 :            :         "const uint slice = wg_idx %% uint(%u);\n"
    2110                 :            :         "const uint hist_base = slice * uint(%u);\n"
    2111                 :            :         "const vec4 color_orig = color;\n"
    2112                 :            :         "_%hx = _%hx = _%hx = 0u;\n",
    2113                 :            :         vars.slices,
    2114                 :            :         vars.hist_bins,
    2115                 :            :         vars.wg_sum,
    2116                 :            :         vars.wg_max,
    2117                 :            :         vars.wg_black
    2118                 :            :     );
    2119                 :            : 
    2120                 :            : if (vars.use_histogram) {
    2121                 :            : #line 1273
    2122                 :            :     pl_str_append_asprintf_c(alloc, buf,
    2123                 :            :         "for (uint i = local_idx; i < uint(%u); i += wg_size)\n"
    2124                 :            :         "_%hx[i] = 0u;\n",
    2125                 :            :         vars.hist_bins,
    2126                 :            :         vars.wg_hist
    2127                 :            :     );
    2128                 :            : 
    2129                 :            : }
    2130                 :            : #line 1276
    2131                 :            :     pl_str_append(alloc, buf, pl_str0(
    2132                 :            :         "barrier();\n"
    2133                 :            :     ));
    2134                 :            : 
    2135                 :            : 
    2136                 :            : return sizeof(vars);
    2137                 :            : }
    2138                 :            : #line 1283
    2139                 :            : size_t _glsl_1284_fn(void *alloc, pl_str *buf, const uint8_t *ptr);
    2140                 :            : size_t _glsl_1284_fn(void *alloc, pl_str *buf, const uint8_t *ptr)
    2141                 :            : {
    2142                 :            : struct __attribute__((__packed__)) {
    2143                 :            :     float pl_color_sdr_white_10000_0;
    2144                 :            :     float pq_m1;
    2145                 :            :     float pq_c1;
    2146                 :            :     float pq_c2;
    2147                 :            :     float pq_c3;
    2148                 :            :     float pq_m2;
    2149                 :            :     float pq_max;
    2150                 :            :     int pq_bits_hist_bits;
    2151                 :            :     int hist_bias;
    2152                 :            :     int hist_bins_1;
    2153                 :            :     unsigned hist_bins;
    2154                 :            :     ident_t sh_luma_coeffs_sh_csp;
    2155                 :            :     ident_t cutoff_;
    2156                 :            :     ident_t wg_hist;
    2157                 :            :     ident_t wg_sum;
    2158                 :            :     ident_t wg_max;
    2159                 :            :     ident_t wg_black;
    2160                 :            :     bool cutoff;
    2161                 :            :     bool use_histogram;
    2162                 :            :     bool has_subgroups;
    2163                 :            : } vars;
    2164                 :            : memcpy(&vars, ptr, sizeof(vars));
    2165                 :            : 
    2166                 :            : #line 1284
    2167                 :            :     pl_str_append_asprintf_c(alloc, buf,
    2168                 :            :         "\n"
    2169                 :            :         "float luma = dot(_%hx, color.rgb);\n"
    2170                 :            :         "luma *= float(%f);\n"
    2171                 :            :         "luma = pow(clamp(luma, 0.0, 1.0), float(%f));\n"
    2172                 :            :         "luma = (float(%f) + float(%f) * luma) /\n"
    2173                 :            :         "(1.0 + float(%f) * luma);\n"
    2174                 :            :         "luma = pow(luma, float(%f));\n",
    2175                 :            :         vars.sh_luma_coeffs_sh_csp,
    2176                 :            :         vars.pl_color_sdr_white_10000_0,
    2177                 :            :         vars.pq_m1,
    2178                 :            :         vars.pq_c1,
    2179                 :            :         vars.pq_c2,
    2180                 :            :         vars.pq_c3,
    2181                 :            :         vars.pq_m2
    2182                 :            :     );
    2183                 :            : 
    2184                 :            : if (vars.cutoff)
    2185                 :            : #line 1292
    2186                 :            :     pl_str_append_asprintf_c(alloc, buf,
    2187                 :            :         "luma *= smoothstep(0.0, _%hx, luma);\n",
    2188                 :            :         vars.cutoff_
    2189                 :            :     );
    2190                 :            : 
    2191                 :            : #line 1293
    2192                 :            :     pl_str_append_asprintf_c(alloc, buf,
    2193                 :            :         "uint y_pq = uint(float(%f) * luma);\n"
    2194                 :            :         "\n",
    2195                 :            :         vars.pq_max
    2196                 :            :     );
    2197                 :            : 
    2198                 :            : if (vars.use_histogram) {
    2199                 :            : #line 1297
    2200                 :            :     pl_str_append_asprintf_c(alloc, buf,
    2201                 :            :         "int bin = int(y_pq) >> %d;\n"
    2202                 :            :         "bin -= %d;\n"
    2203                 :            :         "bin = clamp(bin, 0, %d);\n",
    2204                 :            :         vars.pq_bits_hist_bits,
    2205                 :            :         vars.hist_bias,
    2206                 :            :         vars.hist_bins_1
    2207                 :            :     );
    2208                 :            : 
    2209                 :            : if (vars.has_subgroups) {
    2210                 :            : #line 1301
    2211                 :            :     pl_str_append_asprintf_c(alloc, buf,
    2212                 :            :         "\n"
    2213                 :            :         "if (subgroupAllEqual(bin)) {\n"
    2214                 :            :         "if (subgroupElect())\n"
    2215                 :            :         "atomicAdd(_%hx[bin], gl_SubgroupSize);\n"
    2216                 :            :         "} else {\n"
    2217                 :            :         "atomicAdd(_%hx[bin], 1u);\n"
    2218                 :            :         "}\n",
    2219                 :            :         vars.wg_hist,
    2220                 :            :         vars.wg_hist
    2221                 :            :     );
    2222                 :            : 
    2223                 :            : } else {
    2224                 :            : #line 1309
    2225                 :            :     pl_str_append_asprintf_c(alloc, buf,
    2226                 :            :         "atomicAdd(_%hx[bin], 1u);\n",
    2227                 :            :         vars.wg_hist
    2228                 :            :     );
    2229                 :            : 
    2230                 :            : }
    2231                 :            : }
    2232                 :            : if (vars.has_subgroups) {
    2233                 :            : #line 1314
    2234                 :            :     pl_str_append(alloc, buf, pl_str0(
    2235                 :            :         "uint group_sum = subgroupAdd(y_pq);\n"
    2236                 :            :         "uint group_max = subgroupMax(y_pq);\n"
    2237                 :            :     ));
    2238                 :            : 
    2239                 :            : if (vars.cutoff)
    2240                 :            : #line 1317
    2241                 :            :     pl_str_append(alloc, buf, pl_str0(
    2242                 :            :         "uvec4 b = subgroupBallot(y_pq == 0u);\n"
    2243                 :            :     ));
    2244                 :            : 
    2245                 :            : #line 1318
    2246                 :            :     pl_str_append_asprintf_c(alloc, buf,
    2247                 :            :         "if (subgroupElect()) {\n"
    2248                 :            :         "atomicAdd(_%hx, group_sum);\n"
    2249                 :            :         "atomicMax(_%hx, group_max);\n",
    2250                 :            :         vars.wg_sum,
    2251                 :            :         vars.wg_max
    2252                 :            :     );
    2253                 :            : 
    2254                 :            : if (vars.cutoff)
    2255                 :            : #line 1322
    2256                 :            :     pl_str_append_asprintf_c(alloc, buf,
    2257                 :            :         "atomicAdd(_%hx, subgroupBallotBitCount(b));\n",
    2258                 :            :         vars.wg_black
    2259                 :            :     );
    2260                 :            : 
    2261                 :            : #line 1323
    2262                 :            :     pl_str_append(alloc, buf, pl_str0(
    2263                 :            :         "}\n"
    2264                 :            :     ));
    2265                 :            : 
    2266                 :            : } else {
    2267                 :            : #line 1325
    2268                 :            :     pl_str_append_asprintf_c(alloc, buf,
    2269                 :            :         "atomicAdd(_%hx, y_pq);\n"
    2270                 :            :         "atomicMax(_%hx, y_pq);\n",
    2271                 :            :         vars.wg_sum,
    2272                 :            :         vars.wg_max
    2273                 :            :     );
    2274                 :            : 
    2275                 :            : if (vars.cutoff) {
    2276                 :            : #line 1328
    2277                 :            :     pl_str_append_asprintf_c(alloc, buf,
    2278                 :            :         "if (y_pq == 0u)\n"
    2279                 :            :         "atomicAdd(_%hx, 1u);\n",
    2280                 :            :         vars.wg_black
    2281                 :            :     );
    2282                 :            : 
    2283                 :            : }
    2284                 :            : }
    2285                 :            : #line 1332
    2286                 :            :     pl_str_append(alloc, buf, pl_str0(
    2287                 :            :         "barrier();\n"
    2288                 :            :     ));
    2289                 :            : 
    2290                 :            : if (vars.use_histogram) {
    2291                 :            : if (vars.cutoff) {
    2292                 :            : #line 1336
    2293                 :            :     pl_str_append_asprintf_c(alloc, buf,
    2294                 :            :         "if (gl_LocalInvocationIndex == 0u)\n"
    2295                 :            :         "_%hx[0] -= _%hx;\n",
    2296                 :            :         vars.wg_hist,
    2297                 :            :         vars.wg_black
    2298                 :            :     );
    2299                 :            : 
    2300                 :            : }
    2301                 :            : #line 1339
    2302                 :            :     pl_str_append_asprintf_c(alloc, buf,
    2303                 :            :         "\n"
    2304                 :            :         "for (uint i = local_idx; i < uint(%u); i += wg_size)\n"
    2305                 :            :         "atomicAdd(frame_hist[hist_base + i], _%hx[i]);\n",
    2306                 :            :         vars.hist_bins,
    2307                 :            :         vars.wg_hist
    2308                 :            :     );
    2309                 :            : 
    2310                 :            : }
    2311                 :            : #line 1344
    2312                 :            :     pl_str_append_asprintf_c(alloc, buf,
    2313                 :            :         "\n"
    2314                 :            :         "if (gl_LocalInvocationIndex == 0u) {\n"
    2315                 :            :         "uint num = wg_size - _%hx;\n"
    2316                 :            :         "atomicAdd(frame_wg_count[slice], 1u);\n"
    2317                 :            :         "atomicAdd(frame_wg_active[slice], min(num, 1u));\n"
    2318                 :            :         "if (num > 0u) {\n"
    2319                 :            :         "atomicAdd(frame_sum_pq[slice], _%hx / num);\n"
    2320                 :            :         "atomicMax(frame_max_pq[slice], _%hx);\n"
    2321                 :            :         "}\n"
    2322                 :            :         "}\n"
    2323                 :            :         "color = color_orig;\n"
    2324                 :            :         "}\n",
    2325                 :            :         vars.wg_black,
    2326                 :            :         vars.wg_sum,
    2327                 :            :         vars.wg_max
    2328                 :            :     );
    2329                 :            : 
    2330                 :            : 
    2331                 :            : return sizeof(vars);
    2332                 :            : }

Generated by: LCOV version 1.16