LCOV - code coverage report
Current view: top level - src/shaders - lut.c (source / functions) Hit Total Coverage
Test: Code coverage Lines: 261 353 73.9 %
Date: 2025-03-29 09:04:10 Functions: 7 7 100.0 %
Legend: Lines: hit not hit | Branches: + taken - not taken # not executed Branches: 190 317 59.9 %

           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                 :            : #include <ctype.h>
      20                 :            : 
      21                 :            : #include "shaders.h"
      22                 :            : 
      23                 :            : #include <libplacebo/shaders/lut.h>
      24                 :            : 
      25                 :            : static inline bool isnumeric(char c)
      26                 :            : {
      27                 :         35 :     return (c >= '0' && c <= '9') || c == '-';
      28                 :            : }
      29                 :            : 
      30                 :         11 : void pl_lut_free(struct pl_custom_lut **lut)
      31                 :            : {
      32                 :         11 :     pl_free_ptr(lut);
      33                 :         11 : }
      34                 :            : 
      35                 :         11 : struct pl_custom_lut *pl_lut_parse_cube(pl_log log, const char *cstr, size_t cstr_len)
      36                 :            : {
      37                 :         11 :     struct pl_custom_lut *lut = pl_zalloc_ptr(NULL, lut);
      38                 :         11 :     pl_str str = (pl_str) { (uint8_t *) cstr, cstr_len };
      39                 :         11 :     lut->signature = pl_str_hash(str);
      40                 :            :     int entries = 0;
      41                 :            : 
      42                 :         11 :     float min[3] = { 0.0, 0.0, 0.0 };
      43                 :         11 :     float max[3] = { 1.0, 1.0, 1.0 };
      44                 :            : 
      45                 :            :     // Parse header
      46   [ +  -  +  + ]:         35 :     while (str.len && !isnumeric(str.buf[0])) {
      47                 :         24 :         pl_str line = pl_str_strip(pl_str_getline(str, &str));
      48         [ -  + ]:         24 :         if (!line.len)
      49                 :         24 :             continue; // skip empty line
      50                 :            : 
      51         [ +  + ]:         24 :         if (pl_str_eatstart0(&line, "TITLE")) {
      52         [ +  - ]:         11 :             pl_info(log, "Loading LUT: %.*s", PL_STR_FMT(pl_str_strip(line)));
      53                 :         11 :             continue;
      54                 :            :         }
      55                 :            : 
      56         [ +  + ]:         13 :         if (pl_str_eatstart0(&line, "LUT_3D_SIZE")) {
      57                 :          5 :             line = pl_str_strip(line);
      58                 :            :             int size;
      59         [ -  + ]:          5 :             if (!pl_str_parse_int(line, &size)) {
      60         [ #  # ]:          0 :                 pl_err(log, "Failed parsing dimension '%.*s'", PL_STR_FMT(line));
      61                 :          0 :                 goto error;
      62                 :            :             }
      63         [ -  + ]:          5 :             if (size <= 0 || size > 1024) {
      64                 :          0 :                 pl_err(log, "Invalid 3DLUT size: %dx%d%x", size, size, size);
      65                 :          0 :                 goto error;
      66                 :            :             }
      67                 :            : 
      68                 :          5 :             lut->size[0] = lut->size[1] = lut->size[2] = size;
      69                 :          5 :             entries = size * size * size;
      70                 :          5 :             continue;
      71                 :            :         }
      72                 :            : 
      73         [ +  + ]:          8 :         if (pl_str_eatstart0(&line, "LUT_1D_SIZE")) {
      74                 :          6 :             line = pl_str_strip(line);
      75                 :            :             int size;
      76         [ -  + ]:          6 :             if (!pl_str_parse_int(line, &size)) {
      77         [ #  # ]:          0 :                 pl_err(log, "Failed parsing dimension '%.*s'", PL_STR_FMT(line));
      78                 :          0 :                 goto error;
      79                 :            :             }
      80         [ -  + ]:          6 :             if (size <= 0 || size > 65536) {
      81                 :          0 :                 pl_err(log, "Invalid 1DLUT size: %d", size);
      82                 :          0 :                 goto error;
      83                 :            :             }
      84                 :            : 
      85                 :          6 :             lut->size[0] = size;
      86                 :          6 :             lut->size[1] = lut->size[2] = 0;
      87                 :            :             entries = size;
      88                 :          6 :             continue;
      89                 :            :         }
      90                 :            : 
      91         [ -  + ]:          2 :         if (pl_str_eatstart0(&line, "DOMAIN_MIN")) {
      92                 :          0 :             line = pl_str_strip(line);
      93   [ #  #  #  # ]:          0 :             if (!pl_str_parse_float(pl_str_split_char(line, ' ', &line), &min[0]) ||
      94         [ #  # ]:          0 :                 !pl_str_parse_float(pl_str_split_char(line, ' ', &line), &min[1]) ||
      95                 :          0 :                 !pl_str_parse_float(line, &min[2]))
      96                 :            :             {
      97         [ #  # ]:          0 :                 pl_err(log, "Failed parsing domain: '%.*s'", PL_STR_FMT(line));
      98                 :          0 :                 goto error;
      99                 :            :             }
     100                 :          0 :             continue;
     101                 :            :         }
     102                 :            : 
     103         [ +  + ]:          2 :         if (pl_str_eatstart0(&line, "DOMAIN_MAX")) {
     104                 :          1 :             line = pl_str_strip(line);
     105   [ +  -  +  - ]:          2 :             if (!pl_str_parse_float(pl_str_split_char(line, ' ', &line), &max[0]) ||
     106         [ -  + ]:          2 :                 !pl_str_parse_float(pl_str_split_char(line, ' ', &line), &max[1]) ||
     107                 :          1 :                 !pl_str_parse_float(line, &max[2]))
     108                 :            :             {
     109         [ #  # ]:          0 :                 pl_err(log, "Failed parsing domain: '%.*s'", PL_STR_FMT(line));
     110                 :          0 :                 goto error;
     111                 :            :             }
     112                 :          1 :             continue;
     113                 :            :         }
     114                 :            : 
     115         [ +  - ]:          1 :         if (pl_str_eatstart0(&line, "#")) {
     116         [ +  - ]:          1 :             pl_debug(log, "Unhandled .cube comment: %.*s",
     117                 :            :                      PL_STR_FMT(pl_str_strip(line)));
     118                 :          1 :             continue;
     119                 :            :         }
     120                 :            : 
     121         [ #  # ]:          0 :         pl_warn(log, "Unhandled .cube line: %.*s", PL_STR_FMT(pl_str_strip(line)));
     122                 :            :     }
     123                 :            : 
     124         [ -  + ]:         11 :     if (!entries) {
     125                 :          0 :         pl_err(log, "Missing LUT size specification?");
     126                 :          0 :         goto error;
     127                 :            :     }
     128                 :            : 
     129         [ +  + ]:         44 :     for (int i = 0; i < 3; i++) {
     130         [ -  + ]:         33 :         if (max[i] - min[i] < 1e-6) {
     131                 :          0 :             pl_err(log, "Invalid domain range: [%f, %f]", min[i], max[i]);
     132                 :          0 :             goto error;
     133                 :            :         }
     134                 :            :     }
     135                 :            : 
     136                 :         11 :     float *data = pl_alloc(lut, sizeof(float[3]) * entries);
     137                 :         11 :     lut->data = data;
     138                 :            : 
     139                 :            :     // Parse LUT body
     140                 :            :     pl_clock_t start = pl_clock_now();
     141         [ +  + ]:         92 :     for (int n = 0; n < entries; n++) {
     142         [ +  + ]:        324 :         for (int c = 0; c < 3; c++) {
     143                 :            :             static const char * const digits = "0123456789.-+e";
     144                 :            : 
     145                 :            :             // Extract valid digit sequence
     146                 :        243 :             size_t len = pl_strspn(str, digits);
     147                 :        243 :             pl_str entry = (pl_str) { str.buf, len };
     148                 :        243 :             str.buf += len;
     149                 :        243 :             str.len -= len;
     150                 :            : 
     151         [ -  + ]:        243 :             if (!entry.len) {
     152         [ #  # ]:          0 :                 if (!str.len) {
     153                 :          0 :                     pl_err(log, "Failed parsing LUT: Unexpected EOF, expected "
     154                 :            :                            "%d entries, got %d", entries * 3, n * 3 + c + 1);
     155                 :            :                 } else {
     156                 :          0 :                     pl_err(log, "Failed parsing LUT: Unexpected '%c', expected "
     157                 :            :                            "digit", str.buf[0]);
     158                 :            :                 }
     159                 :          0 :                 goto error;
     160                 :            :             }
     161                 :            : 
     162                 :            :             float num;
     163         [ -  + ]:        243 :             if (!pl_str_parse_float(entry, &num)) {
     164         [ #  # ]:          0 :                 pl_err(log, "Failed parsing float value '%.*s'", PL_STR_FMT(entry));
     165                 :          0 :                 goto error;
     166                 :            :             }
     167                 :            : 
     168                 :            :             // Rescale to range 0.0 - 1.0
     169                 :        243 :             *data++ = (num - min[c]) / (max[c] - min[c]);
     170                 :            : 
     171                 :            :             // Skip whitespace between digits
     172                 :        243 :             str = pl_str_strip(str);
     173                 :            :         }
     174                 :            :     }
     175                 :            : 
     176                 :         11 :     str = pl_str_strip(str);
     177         [ -  + ]:         11 :     if (str.len)
     178                 :          0 :         pl_warn(log, "Extra data after LUT?... ignoring '%c'", str.buf[0]);
     179                 :            : 
     180                 :         11 :     pl_log_cpu_time(log, start, pl_clock_now(), "parsing .cube LUT");
     181                 :         11 :     return lut;
     182                 :            : 
     183                 :          0 : error:
     184                 :          0 :     pl_free(lut);
     185                 :          0 :     return NULL;
     186                 :            : }
     187                 :            : 
     188                 :         27 : static void fill_lut(void *datap, const struct sh_lut_params *params)
     189                 :            : {
     190                 :         27 :     const struct pl_custom_lut *lut = params->priv;
     191                 :            : 
     192                 :         27 :     int dim_r = params->width;
     193         [ +  + ]:         27 :     int dim_g = PL_DEF(params->height, 1);
     194         [ +  + ]:         27 :     int dim_b = PL_DEF(params->depth, 1);
     195                 :            : 
     196                 :            :     float *data = datap;
     197         [ +  + ]:         68 :     for (int b = 0; b < dim_b; b++) {
     198         [ +  + ]:        112 :         for (int g = 0; g < dim_g; g++) {
     199         [ +  + ]:        232 :             for (int r = 0; r < dim_r; r++) {
     200                 :        161 :                 size_t offset = (b * dim_g + g) * dim_r + r;
     201                 :        161 :                 const float *src = &lut->data[offset * 3];
     202                 :        161 :                 float *dst = &data[offset * 4];
     203                 :        161 :                 dst[0] = src[0];
     204                 :        161 :                 dst[1] = src[1];
     205                 :        161 :                 dst[2] = src[2];
     206                 :        161 :                 dst[3] = 0.0f;
     207                 :            :             }
     208                 :            :         }
     209                 :            :     }
     210                 :         27 : }
     211                 :            : 
     212                 :         99 : void pl_shader_custom_lut(pl_shader sh, const struct pl_custom_lut *lut,
     213                 :            :                           pl_shader_obj *lut_state)
     214                 :            : {
     215         [ +  - ]:         99 :     if (!lut)
     216                 :          0 :         return;
     217                 :            : 
     218                 :            :     int dims;
     219   [ +  -  +  +  :         99 :     if (lut->size[0] > 0 && lut->size[1] > 0 && lut->size[2] > 0) {
                   -  + ]
     220                 :            :         dims = 3;
     221   [ +  -  +  -  :         50 :     } else if (lut->size[0] > 0 && !lut->size[1] && !lut->size[2]) {
                   -  + ]
     222                 :            :         dims = 1;
     223                 :            :     } else {
     224                 :          0 :         SH_FAIL(sh, "Invalid dimensions %dx%dx%d for pl_custom_lut, must be 1D "
     225                 :            :                 "or 3D!", lut->size[0], lut->size[1], lut->size[2]);
     226                 :          0 :         return;
     227                 :            :     }
     228                 :            : 
     229         [ +  - ]:         99 :     if (!sh_require(sh, PL_SHADER_SIG_COLOR, 0, 0))
     230                 :            :         return;
     231                 :            : 
     232                 :         99 :     ident_t fun = sh_lut(sh, sh_lut_params(
     233                 :            :         .object     = lut_state,
     234                 :            :         .var_type   = PL_VAR_FLOAT,
     235                 :            :         .method     = SH_LUT_TETRAHEDRAL,
     236                 :            :         .width      = lut->size[0],
     237                 :            :         .height     = lut->size[1],
     238                 :            :         .depth      = lut->size[2],
     239                 :            :         .comps      = 4, // for better texel alignment
     240                 :            :         .signature  = lut->signature,
     241                 :            :         .fill       = fill_lut,
     242                 :            :         .priv       = (void *) lut,
     243                 :            :     ));
     244                 :            : 
     245         [ -  + ]:         99 :     if (!fun) {
     246                 :          0 :         SH_FAIL(sh, "pl_shader_custom_lut: failed generating LUT object");
     247                 :          0 :         return;
     248                 :            :     }
     249                 :            : 
     250                 :         99 :     GLSL("// pl_shader_custom_lut \n");
     251                 :            : 
     252                 :            :     static const pl_matrix3x3 zero = {0};
     253         [ -  + ]:         99 :     if (memcmp(&lut->shaper_in, &zero, sizeof(zero)) != 0) {
     254                 :          0 :         GLSL("color.rgb = "$" * color.rgb; \n", sh_var(sh, (struct pl_shader_var) {
     255                 :            :             .var = pl_var_mat3("shaper_in"),
     256                 :            :             .data = PL_TRANSPOSE_3X3(lut->shaper_in.m),
     257                 :            :         }));
     258                 :            :     }
     259                 :            : 
     260      [ +  +  - ]:         99 :     switch (dims) {
     261                 :         50 :     case 1:
     262                 :         50 :         sh_describe(sh, "custom 1DLUT");
     263                 :         50 :         GLSL("color.rgb = vec3("$"(color.r).r,  \n"
     264                 :            :              "                 "$"(color.g).g,  \n"
     265                 :            :              "                 "$"(color.b).b); \n",
     266                 :            :              fun, fun, fun);
     267                 :            :         break;
     268                 :         49 :     case 3:
     269                 :         49 :         sh_describe(sh, "custom 3DLUT");
     270                 :         49 :         GLSL("color.rgb = "$"(color.rgb).rgb; \n", fun);
     271                 :            :         break;
     272                 :            :     }
     273                 :            : 
     274         [ -  + ]:         99 :     if (memcmp(&lut->shaper_out, &zero, sizeof(zero)) != 0) {
     275                 :          0 :         GLSL("color.rgb = "$" * color.rgb; \n", sh_var(sh, (struct pl_shader_var) {
     276                 :            :             .var = pl_var_mat3("shaper_out"),
     277                 :            :             .data = PL_TRANSPOSE_3X3(lut->shaper_out.m),
     278                 :            :         }));
     279                 :            :     }
     280                 :            : }
     281                 :            : 
     282                 :            : // Defines a LUT position helper macro. This translates from an absolute texel
     283                 :            : // scale (either in texels, or normalized to [0,1]) to the texture coordinate
     284                 :            : // scale for the corresponding sample in a texture of dimension `lut_size`.
     285                 :        672 : static ident_t texel_scale(pl_shader sh, int lut_size, bool normalized)
     286                 :            : {
     287                 :        672 :     const float base = 0.5f / lut_size;
     288                 :        672 :     const float end = 1.0f - 0.5f / lut_size;
     289         [ -  + ]:        672 :     const float scale = (end - base) / (normalized ? 1.0f : (lut_size - 1));
     290                 :            : 
     291                 :        672 :     ident_t name = sh_fresh(sh, "LUT_SCALE");
     292                 :        672 :     GLSLH("#define "$"(x) ("$" * (x) + "$") \n",
     293                 :            :           name, SH_FLOAT(scale), SH_FLOAT(base));
     294                 :        672 :     return name;
     295                 :            : }
     296                 :            : 
     297                 :            : struct sh_lut_obj {
     298                 :            :     enum sh_lut_type type;
     299                 :            :     enum sh_lut_method method;
     300                 :            :     enum pl_var_type vartype;
     301                 :            :     pl_fmt fmt;
     302                 :            :     int width, height, depth, comps;
     303                 :            :     uint64_t signature;
     304                 :            :     bool error; // reset if params change
     305                 :            : 
     306                 :            :     // weights, depending on the lut type
     307                 :            :     pl_tex tex;
     308                 :            :     pl_str str;
     309                 :            :     void *data;
     310                 :            : };
     311                 :            : 
     312                 :         83 : static void sh_lut_uninit(pl_gpu gpu, void *ptr)
     313                 :            : {
     314                 :            :     struct sh_lut_obj *lut = ptr;
     315                 :         83 :     pl_tex_destroy(gpu, &lut->tex);
     316                 :         83 :     pl_free(lut->str.buf);
     317                 :         83 :     pl_free(lut->data);
     318                 :            : 
     319                 :         83 :     *lut = (struct sh_lut_obj) {0};
     320                 :         83 : }
     321                 :            : 
     322                 :            : // Maximum number of floats to embed as a literal array (when using SH_LUT_AUTO)
     323                 :            : #define SH_LUT_MAX_LITERAL_SOFT 64
     324                 :            : #define SH_LUT_MAX_LITERAL_HARD 256
     325                 :            : 
     326                 :        628 : ident_t sh_lut(pl_shader sh, const struct sh_lut_params *params)
     327                 :            : {
     328                 :        628 :     pl_gpu gpu = SH_GPU(sh);
     329                 :        628 :     pl_cache_obj obj = { .key = CACHE_KEY_SH_LUT ^ params->signature };
     330                 :            : 
     331                 :        628 :     const enum pl_var_type vartype = params->var_type;
     332         [ -  + ]:        628 :     pl_assert(vartype != PL_VAR_INVALID);
     333   [ +  +  -  + ]:        628 :     pl_assert(params->method == SH_LUT_NONE || vartype == PL_VAR_FLOAT);
     334   [ +  -  +  -  :        628 :     pl_assert(params->width > 0 && params->height >= 0 && params->depth >= 0);
                   -  + ]
     335         [ -  + ]:        628 :     pl_assert(params->comps > 0);
     336   [ -  +  -  - ]:        628 :     pl_assert(!params->cache || params->signature);
     337                 :            : 
     338                 :        628 :     int sizes[] = { params->width, params->height, params->depth };
     339   [ +  +  +  + ]:       1090 :     int size = params->width * PL_DEF(params->height, 1) * PL_DEF(params->depth, 1);
     340   [ +  +  +  + ]:        628 :     int dims = params->depth ? 3 : params->height ? 2 : 1;
     341                 :            :     enum sh_lut_method method = params->method;
     342         [ +  + ]:        628 :     if (method == SH_LUT_TETRAHEDRAL && dims != 3)
     343                 :            :         method = SH_LUT_LINEAR;
     344         [ -  + ]:        628 :     if (method == SH_LUT_CUBIC && dims != 3)
     345                 :            :         method = SH_LUT_LINEAR;
     346                 :            : 
     347                 :            :     int texdim = 0;
     348                 :       1884 :     uint32_t max_tex_dim[] = {
     349         [ +  + ]:        628 :         gpu ? gpu->limits.max_tex_1d_dim : 0,
     350         [ +  + ]:        628 :         gpu ? gpu->limits.max_tex_2d_dim : 0,
     351   [ +  +  +  - ]:        628 :         (gpu && gpu->glsl.version > 100) ? gpu->limits.max_tex_3d_dim : 0,
     352                 :            :     };
     353                 :            : 
     354                 :        628 :     struct sh_lut_obj *lut = SH_OBJ(sh, params->object, PL_SHADER_OBJ_LUT,
     355                 :            :                                     struct sh_lut_obj, sh_lut_uninit);
     356                 :            : 
     357         [ -  + ]:        628 :     if (!lut)
     358                 :            :         return NULL_IDENT;
     359                 :            : 
     360         [ +  + ]:        327 :     bool update = params->update || lut->signature != params->signature ||
     361   [ -  +  -  + ]:        259 :                   vartype != lut->vartype || params->fmt != lut->fmt ||
     362         [ -  + ]:        259 :                   params->width != lut->width || params->height != lut->height ||
     363   [ +  +  -  + ]:        887 :                   params->depth != lut->depth || params->comps != lut->comps;
     364                 :            : 
     365   [ +  -  -  - ]:        628 :     if (lut->error && !update)
     366                 :            :         return NULL_IDENT; // suppress error spam until something changes
     367                 :            : 
     368                 :            :     // Try picking the right number of dimensions for the texture LUT. This
     369                 :            :     // allows e.g. falling back to 2D textures if 1D textures are unsupported.
     370         [ +  + ]:        630 :     for (int d = dims; d <= PL_ARRAY_SIZE(max_tex_dim); d++) {
     371                 :            :         // For a given dimension to be compatible, all coordinates need to be
     372                 :            :         // within the maximum texture size for that dimension
     373         [ +  + ]:       1812 :         for (int i = 0; i < d; i++) {
     374         [ +  + ]:       1185 :             if (sizes[i] > max_tex_dim[d - 1])
     375                 :          2 :                 goto next_dim;
     376                 :            :         }
     377                 :            : 
     378                 :            :         // All dimensions are compatible, so pick this texture dimension
     379                 :            :         texdim = d;
     380                 :            :         break;
     381                 :            : 
     382                 :            : next_dim: ; // `continue` out of the inner loop
     383                 :            :     }
     384                 :            : 
     385                 :            :     static const enum pl_fmt_type fmt_type[PL_VAR_TYPE_COUNT] = {
     386                 :            :         [PL_VAR_SINT]   = PL_FMT_SINT,
     387                 :            :         [PL_VAR_UINT]   = PL_FMT_UINT,
     388                 :            :         [PL_VAR_FLOAT]  = PL_FMT_FLOAT,
     389                 :            :     };
     390                 :            : 
     391                 :            :     enum pl_fmt_caps texcaps = PL_FMT_CAP_SAMPLEABLE;
     392                 :        628 :     bool is_linear = method == SH_LUT_LINEAR || method == SH_LUT_CUBIC;
     393         [ +  + ]:        628 :     if (is_linear)
     394                 :            :         texcaps |= PL_FMT_CAP_LINEAR;
     395                 :            : 
     396                 :        628 :     pl_fmt texfmt = params->fmt;
     397         [ +  + ]:        628 :     if (texfmt) {
     398                 :            :         bool ok;
     399      [ -  -  + ]:         46 :         switch (texfmt->type) {
     400                 :          0 :         case PL_FMT_SINT: ok = vartype == PL_VAR_SINT; break;
     401                 :          0 :         case PL_FMT_UINT: ok = vartype == PL_VAR_UINT; break;
     402                 :         46 :         default:          ok = vartype == PL_VAR_FLOAT; break;
     403                 :            :         }
     404                 :            : 
     405         [ -  + ]:         46 :         if (!ok) {
     406                 :          0 :             PL_ERR(sh, "Specified texture format '%s' does not match LUT "
     407                 :            :                    "data type!", texfmt->name);
     408                 :          0 :             goto error;
     409                 :            :         }
     410                 :            : 
     411         [ -  + ]:         46 :         if (~texfmt->caps & texcaps) {
     412                 :          0 :             PL_ERR(sh, "Specified texture format '%s' does not match "
     413                 :            :                    "required capabilities 0x%x!\n", texfmt->name, texcaps);
     414                 :          0 :             goto error;
     415                 :            :         }
     416                 :            :     }
     417                 :            : 
     418         [ +  + ]:        628 :     if (texdim && !texfmt) {
     419         [ +  + ]:        589 :         texfmt = pl_find_fmt(gpu, fmt_type[vartype], params->comps,
     420                 :            :                              vartype == PL_VAR_FLOAT ? 16 : 32,
     421                 :        581 :                              pl_var_type_size(vartype) * 8,
     422                 :            :                              texcaps);
     423                 :            :     }
     424                 :            : 
     425                 :        628 :     enum sh_lut_type type = params->lut_type;
     426                 :            : 
     427                 :            :     // The linear sampling code currently only supports 1D linear interpolation
     428         [ +  + ]:        628 :     if (is_linear && dims > 1) {
     429         [ -  + ]:        238 :         if (texfmt) {
     430                 :            :             type = SH_LUT_TEXTURE;
     431                 :            :         } else {
     432                 :          0 :             PL_ERR(sh, "Can't emulate linear LUTs for 2D/3D LUTs and no "
     433                 :            :                   "texture support available!");
     434                 :          0 :             goto error;
     435                 :            :         }
     436                 :            :     }
     437                 :            : 
     438   [ +  +  +  + ]:        628 :     bool can_uniform = gpu && gpu->limits.max_variable_comps >= size * params->comps;
     439                 :        628 :     bool can_literal = sh_glsl(sh).version > 110; // needed for literal arrays
     440   [ +  +  +  + ]:        628 :     can_literal &= size <= SH_LUT_MAX_LITERAL_HARD && !params->dynamic;
     441                 :            : 
     442                 :            :     // Deselect unsupported methods
     443         [ -  + ]:        628 :     if (type == SH_LUT_UNIFORM && !can_uniform)
     444                 :            :         type = SH_LUT_AUTO;
     445         [ -  + ]:        628 :     if (type == SH_LUT_LITERAL && !can_literal)
     446                 :            :         type = SH_LUT_AUTO;
     447         [ -  + ]:        628 :     if (type == SH_LUT_TEXTURE && !texfmt)
     448                 :            :         type = SH_LUT_AUTO;
     449                 :            : 
     450                 :            :     // Sorted by priority
     451   [ +  +  -  + ]:        628 :     if (!type && can_literal && !method && size <= SH_LUT_MAX_LITERAL_SOFT)
     452                 :            :         type = SH_LUT_LITERAL;
     453         [ +  + ]:        628 :     if (!type && texfmt)
     454                 :            :         type = SH_LUT_TEXTURE;
     455         [ +  + ]:        628 :     if (!type && can_uniform)
     456                 :            :         type = SH_LUT_UNIFORM;
     457         [ +  - ]:        628 :     if (!type && can_literal)
     458                 :            :         type = SH_LUT_LITERAL;
     459                 :            : 
     460         [ +  + ]:        628 :     if (!type) {
     461                 :          1 :         PL_ERR(sh, "Can't generate LUT: no compatible methods!");
     462                 :          1 :         goto error;
     463                 :            :     }
     464                 :            : 
     465                 :            :     // Reinitialize the existing LUT if needed
     466                 :        627 :     update |= type != lut->type;
     467                 :        627 :     update |= method != lut->method;
     468                 :            : 
     469         [ +  + ]:        627 :     if (update) {
     470         [ +  + ]:        368 :         if (params->dynamic)
     471                 :         34 :             pl_log_level_cap(sh->log, PL_LOG_TRACE);
     472                 :            : 
     473                 :        368 :         size_t el_size = params->comps * pl_var_type_size(vartype);
     474         [ +  + ]:        368 :         if (type == SH_LUT_TEXTURE)
     475                 :        363 :             el_size = texfmt->texel_size;
     476                 :            : 
     477                 :        368 :         size_t buf_size = size * el_size;
     478   [ -  +  -  - ]:        368 :         if (pl_cache_get(params->cache, &obj) && obj.size == buf_size) {
     479                 :          0 :             PL_DEBUG(sh, "Re-using cached LUT (0x%"PRIx64") with size %zu",
     480                 :            :                      obj.key, obj.size);
     481                 :            :         } else {
     482                 :        368 :             PL_DEBUG(sh, "LUT invalidated, regenerating..");
     483                 :        368 :             pl_cache_obj_resize(NULL, &obj, buf_size);
     484                 :            :             pl_clock_t start = pl_clock_now();
     485                 :        368 :             params->fill(obj.data, params);
     486                 :        368 :             pl_log_cpu_time(sh->log, start, pl_clock_now(), "generating shader LUT");
     487                 :            :         }
     488                 :            : 
     489   [ +  -  -  + ]:        368 :         pl_assert(obj.data && obj.size);
     490         [ +  + ]:        368 :         if (params->dynamic)
     491                 :         34 :             pl_log_level_cap(sh->log, PL_LOG_NONE);
     492                 :            : 
     493   [ +  +  -  - ]:        368 :         switch (type) {
     494                 :        363 :         case SH_LUT_TEXTURE: {
     495         [ -  + ]:        363 :             if (!texdim) {
     496                 :          0 :                 PL_ERR(sh, "Texture LUT exceeds texture dimensions!");
     497                 :          0 :                 goto error;
     498                 :            :             }
     499                 :            : 
     500         [ -  + ]:        363 :             if (!texfmt) {
     501                 :          0 :                 PL_ERR(sh, "Found no compatible texture format for LUT!");
     502                 :          0 :                 goto error;
     503                 :            :             }
     504                 :            : 
     505                 :       1452 :             struct pl_tex_params tex_params = {
     506                 :        363 :                 .w              = params->width,
     507         [ +  + ]:        363 :                 .h              = PL_DEF(params->height, texdim >= 2 ? 1 : 0),
     508         [ +  + ]:        363 :                 .d              = PL_DEF(params->depth,  texdim >= 3 ? 1 : 0),
     509                 :            :                 .format         = texfmt,
     510                 :            :                 .sampleable     = true,
     511                 :        363 :                 .host_writable  = params->dynamic,
     512         [ +  + ]:        363 :                 .initial_data   = params->dynamic ? NULL : obj.data,
     513                 :        363 :                 .debug_tag      = params->debug_tag,
     514                 :            :             };
     515                 :            : 
     516                 :            :             bool ok;
     517         [ +  + ]:        363 :             if (params->dynamic) {
     518                 :         29 :                 ok = pl_tex_recreate(gpu, &lut->tex, &tex_params);
     519         [ +  - ]:         29 :                 if (ok) {
     520                 :         29 :                     ok = pl_tex_upload(gpu, pl_tex_transfer_params(
     521                 :            :                         .tex = lut->tex,
     522                 :            :                         .ptr = obj.data,
     523                 :            :                     ));
     524                 :            :                 }
     525                 :            :             } else {
     526                 :            :                 // Can't use pl_tex_recreate because of `initial_data`
     527                 :        334 :                 pl_tex_destroy(gpu, &lut->tex);
     528                 :        334 :                 lut->tex = pl_tex_create(gpu, &tex_params);
     529                 :        334 :                 ok = lut->tex;
     530                 :            :             }
     531                 :            : 
     532         [ -  + ]:        363 :             if (!ok) {
     533                 :          0 :                 PL_ERR(sh, "Failed creating LUT texture!");
     534                 :          0 :                 goto error;
     535                 :            :             }
     536                 :        363 :             break;
     537                 :            :         }
     538                 :            : 
     539                 :          5 :         case SH_LUT_UNIFORM:
     540                 :          5 :             pl_free(lut->data);
     541                 :          5 :             lut->data = pl_memdup(NULL, obj.data, obj.size);
     542                 :          5 :             break;
     543                 :            : 
     544                 :          0 :         case SH_LUT_LITERAL: {
     545                 :          0 :             lut->str.len = 0;
     546                 :            :             static const char prefix[PL_VAR_TYPE_COUNT] = {
     547                 :            :                 [PL_VAR_SINT]   = 'i',
     548                 :            :                 [PL_VAR_UINT]   = 'u',
     549                 :            :                 [PL_VAR_FLOAT]  = ' ',
     550                 :            :             };
     551                 :            : 
     552         [ #  # ]:          0 :             for (int i = 0; i < size * params->comps; i += params->comps) {
     553         [ #  # ]:          0 :                 if (i > 0)
     554                 :          0 :                     pl_str_append_asprintf_c(lut, &lut->str, ",");
     555         [ #  # ]:          0 :                 if (params->comps > 1) {
     556                 :          0 :                     pl_str_append_asprintf_c(lut, &lut->str, "%cvec%d(",
     557                 :          0 :                                              prefix[vartype], params->comps);
     558                 :            :                 }
     559         [ #  # ]:          0 :                 for (int c = 0; c < params->comps; c++) {
     560   [ #  #  #  #  :          0 :                     switch (vartype) {
                      # ]
     561                 :          0 :                     case PL_VAR_FLOAT:
     562                 :          0 :                         pl_str_append_asprintf_c(lut, &lut->str, "%s%f",
     563                 :            :                                                  c > 0 ? "," : "",
     564         [ #  # ]:          0 :                                                  ((float *) obj.data)[i+c]);
     565                 :          0 :                         break;
     566                 :          0 :                     case PL_VAR_UINT:
     567                 :          0 :                         pl_str_append_asprintf_c(lut, &lut->str, "%s%u",
     568                 :            :                                                  c > 0 ? "," : "",
     569         [ #  # ]:          0 :                                                  ((unsigned int *) obj.data)[i+c]);
     570                 :          0 :                         break;
     571                 :          0 :                     case PL_VAR_SINT:
     572                 :          0 :                         pl_str_append_asprintf_c(lut, &lut->str, "%s%d",
     573                 :            :                                                  c > 0 ? "," : "",
     574         [ #  # ]:          0 :                                                  ((int *) obj.data)[i+c]);
     575                 :          0 :                         break;
     576                 :            :                     case PL_VAR_INVALID:
     577                 :            :                     case PL_VAR_TYPE_COUNT:
     578                 :          0 :                         pl_unreachable();
     579                 :            :                     }
     580                 :            :                 }
     581         [ #  # ]:          0 :                 if (params->comps > 1)
     582                 :          0 :                     pl_str_append_asprintf_c(lut, &lut->str, ")");
     583                 :            :             }
     584                 :            :             break;
     585                 :            :         }
     586                 :            : 
     587                 :            :         case SH_LUT_AUTO:
     588                 :            :             pl_unreachable();
     589                 :            :         }
     590                 :            : 
     591                 :        368 :         lut->type = type;
     592                 :        368 :         lut->method = method;
     593                 :        368 :         lut->vartype = vartype;
     594                 :        368 :         lut->fmt = params->fmt;
     595                 :        368 :         lut->width = params->width;
     596                 :        368 :         lut->height = params->height;
     597                 :        368 :         lut->depth = params->depth;
     598                 :        368 :         lut->comps = params->comps;
     599                 :        368 :         lut->signature = params->signature;
     600                 :        368 :         pl_cache_set(params->cache, &obj);
     601                 :            :     }
     602                 :            : 
     603                 :            :     // Done updating, generate the GLSL
     604                 :        627 :     ident_t name = sh_fresh(sh, "lut");
     605                 :            :     ident_t arr_name = NULL_IDENT;
     606                 :            : 
     607                 :            :     static const char * const swizzles[] = {"x", "xy", "xyz", "xyzw"};
     608                 :            :     static const char * const vartypes[PL_VAR_TYPE_COUNT][4] = {
     609                 :            :         [PL_VAR_SINT] = { "int", "ivec2", "ivec3", "ivec4" },
     610                 :            :         [PL_VAR_UINT] = { "uint", "uvec2", "uvec3", "uvec4" },
     611                 :            :         [PL_VAR_FLOAT] = { "float", "vec2", "vec3", "vec4" },
     612                 :            :     };
     613                 :            : 
     614   [ +  +  -  - ]:        627 :     switch (type) {
     615                 :        622 :     case SH_LUT_TEXTURE: {
     616         [ -  + ]:        622 :         assert(texdim);
     617                 :        622 :         ident_t tex = sh_desc(sh, (struct pl_shader_desc) {
     618                 :            :             .desc = {
     619                 :            :                 .name = "weights",
     620                 :            :                 .type = PL_DESC_SAMPLED_TEX,
     621                 :            :             },
     622                 :            :             .binding = {
     623                 :        622 :                 .object = lut->tex,
     624                 :            :                 .sample_mode = is_linear ? PL_TEX_SAMPLE_LINEAR
     625                 :        622 :                                          : PL_TEX_SAMPLE_NEAREST,
     626                 :            :             }
     627                 :            :         });
     628                 :            : 
     629         [ +  + ]:        622 :         if (is_linear) {
     630                 :        404 :             ident_t pos_macros[PL_ARRAY_SIZE(sizes)] = {0};
     631         [ +  + ]:       1076 :             for (int i = 0; i < dims; i++)
     632                 :        672 :                 pos_macros[i] = texel_scale(sh, sizes[i], true);
     633                 :            : 
     634                 :        404 :             GLSLH("#define "$"(pos) (textureLod("$", %s(\\\n",
     635                 :            :                   name, tex, vartypes[PL_VAR_FLOAT][texdim - 1]);
     636                 :            : 
     637         [ +  + ]:       1076 :             for (int i = 0; i < texdim; i++) {
     638         [ +  + ]:        672 :                 char sep = i == 0 ? ' ' : ',';
     639         [ +  - ]:        672 :                 if (pos_macros[i]) {
     640         [ +  + ]:        672 :                     if (dims > 1) {
     641                 :        506 :                         GLSLH("   %c"$"(%s(pos).%c)\\\n", sep, pos_macros[i],
     642                 :            :                               vartypes[PL_VAR_FLOAT][dims - 1], "xyzw"[i]);
     643                 :            :                     } else {
     644                 :        166 :                         GLSLH("   %c"$"(float(pos))\\\n", sep, pos_macros[i]);
     645                 :            :                     }
     646                 :            :                 } else {
     647                 :          0 :                     GLSLH("   %c%f\\\n", sep, 0.5);
     648                 :            :                 }
     649                 :            :             }
     650                 :        404 :             GLSLH("  ), 0.0).%s)\n", swizzles[params->comps - 1]);
     651                 :            :         } else {
     652                 :        218 :             GLSLH("#define "$"(pos) (texelFetch("$", %s(pos",
     653                 :            :                   name, tex, vartypes[PL_VAR_SINT][texdim - 1]);
     654                 :            : 
     655                 :            :             // Fill up extra components of the index
     656         [ -  + ]:        218 :             for (int i = dims; i < texdim; i++)
     657                 :          0 :                 GLSLH(", 0");
     658                 :            : 
     659                 :        218 :             GLSLH("), 0).%s)\n", swizzles[params->comps - 1]);
     660                 :            :         }
     661                 :            :         break;
     662                 :            :     }
     663                 :            : 
     664                 :          5 :     case SH_LUT_UNIFORM:
     665                 :          5 :         arr_name = sh_var(sh, (struct pl_shader_var) {
     666                 :            :             .var = {
     667                 :            :                 .name = "weights",
     668                 :            :                 .type = vartype,
     669                 :          5 :                 .dim_v = params->comps,
     670                 :            :                 .dim_m = 1,
     671                 :            :                 .dim_a = size,
     672                 :            :             },
     673                 :          5 :             .data = lut->data,
     674                 :            :         });
     675                 :          5 :         break;
     676                 :            : 
     677                 :          0 :     case SH_LUT_LITERAL:
     678                 :          0 :         arr_name = sh_fresh(sh, "weights");
     679                 :          0 :         GLSLH("const %s "$"[%d] = %s[](\n  ",
     680                 :            :               vartypes[vartype][params->comps - 1], arr_name, size,
     681                 :            :               vartypes[vartype][params->comps - 1]);
     682                 :          0 :         sh_append_str(sh, SH_BUF_HEADER, lut->str);
     683                 :          0 :         GLSLH(");\n");
     684                 :            :         break;
     685                 :            : 
     686                 :            :     case SH_LUT_AUTO:
     687                 :            :         pl_unreachable();
     688                 :            :     }
     689                 :            : 
     690         [ -  + ]:          5 :     if (arr_name) {
     691         [ -  + ]:          5 :         GLSLH("#define "$"(pos) ("$"[int((pos)%s)\\\n",
     692                 :            :               name, arr_name, dims > 1 ? "[0]" : "");
     693                 :          5 :         int shift = params->width;
     694         [ +  + ]:         10 :         for (int i = 1; i < dims; i++) {
     695                 :          5 :             GLSLH("    + %d * int((pos)[%d])\\\n", shift, i);
     696                 :          5 :             shift *= sizes[i];
     697                 :            :         }
     698                 :          5 :         GLSLH("  ])\n");
     699                 :            : 
     700         [ +  - ]:          5 :         if (is_linear) {
     701         [ #  # ]:          0 :             pl_assert(dims == 1);
     702         [ #  # ]:          0 :             pl_assert(vartype == PL_VAR_FLOAT);
     703                 :            :             ident_t arr_lut = name;
     704                 :          0 :             name = sh_fresh(sh, "lut_lin");
     705                 :          0 :             GLSLH("%s "$"(float fpos) {                             \n"
     706                 :            :                   "    fpos = clamp(fpos, 0.0, 1.0) * %d.0;         \n"
     707                 :            :                   "    float fbase = floor(fpos);                   \n"
     708                 :            :                   "    float fceil = ceil(fpos);                    \n"
     709                 :            :                   "    float fcoord = fpos - fbase;                 \n"
     710                 :            :                   "    return mix("$"(fbase), "$"(fceil), fcoord);  \n"
     711                 :            :                   "}                                                \n",
     712                 :            :                   vartypes[PL_VAR_FLOAT][params->comps - 1], name,
     713                 :            :                   size - 1,
     714                 :            :                   arr_lut, arr_lut);
     715                 :            :         }
     716                 :            :     }
     717                 :            : 
     718         [ -  + ]:        627 :     if (method == SH_LUT_CUBIC && dims == 3) {
     719                 :            :         ident_t lin_lut = name;
     720                 :          0 :         name = sh_fresh(sh, "lut_tricubic");
     721                 :          0 :         GLSLH("%s "$"(vec3 pos) {                                       \n"
     722                 :            :               "    vec3 scale = vec3(%d.0, %d.0, %d.0);                 \n"
     723                 :            :               "    vec3 scale_inv = 1.0 / scale;                        \n"
     724                 :            :               "    pos *= scale;                                        \n"
     725                 :            :               "    vec3 fpos = fract(pos);                              \n"
     726                 :            :               "    vec3 base = pos - fpos;                              \n"
     727                 :            :               "    vec3 fpos2 = fpos * fpos;                            \n"
     728                 :            :               "    vec3 inv = 1.0 - fpos;                               \n"
     729                 :            :               "    vec3 inv2 = inv * inv;                               \n"
     730                 :            :               "    vec3 w0 = 1.0/6.0 * inv2 * inv;                      \n"
     731                 :            :               "    vec3 w1 = 2.0/3.0 - 0.5 * fpos2 * (2.0 - fpos);      \n"
     732                 :            :               "    vec3 w2 = 2.0/3.0 - 0.5 * inv2 * (2.0 - inv);        \n"
     733                 :            :               "    vec3 w3 = 1.0/6.0 * fpos2 * fpos;                    \n"
     734                 :            :               "    vec3 g0 = w0 + w1;                                   \n"
     735                 :            :               "    vec3 g1 = w2 + w3;                                   \n"
     736                 :            :               "    vec3 h0 = scale_inv * ((w1 / g0) - 1.0 + base);      \n"
     737                 :            :               "    vec3 h1 = scale_inv * ((w3 / g1) + 1.0 + base);      \n"
     738                 :            :               "    %s c000, c001, c010, c011, c100, c101, c110, c111;   \n"
     739                 :            :               "    c000 = "$"(h0);                                      \n"
     740                 :            :               "    c100 = "$"(vec3(h1.x, h0.y, h0.z));                  \n"
     741                 :            :               "    c000 = mix(c100, c000, g0.x);                        \n"
     742                 :            :               "    c010 = "$"(vec3(h0.x, h1.y, h0.z));                  \n"
     743                 :            :               "    c110 = "$"(vec3(h1.x, h1.y, h0.z));                  \n"
     744                 :            :               "    c010 = mix(c110, c010, g0.x);                        \n"
     745                 :            :               "    c000 = mix(c010, c000, g0.y);                        \n"
     746                 :            :               "    c001 = "$"(vec3(h0.x, h0.y, h1.z));                  \n"
     747                 :            :               "    c101 = "$"(vec3(h1.x, h0.y, h1.z));                  \n"
     748                 :            :               "    c001 = mix(c101, c001, g0.x);                        \n"
     749                 :            :               "    c011 = "$"(vec3(h0.x, h1.y, h1.z));                  \n"
     750                 :            :               "    c111 = "$"(h1);                                      \n"
     751                 :            :               "    c011 = mix(c111, c011, g0.x);                        \n"
     752                 :            :               "    c001 = mix(c011, c001, g0.y);                        \n"
     753                 :            :               "    return mix(c001, c000, g0.z);                        \n"
     754                 :            :               "}                                                        \n",
     755                 :            :               vartypes[PL_VAR_FLOAT][params->comps - 1], name,
     756                 :            :               sizes[0] - 1, sizes[1] - 1, sizes[2] - 1,
     757                 :            :               vartypes[PL_VAR_FLOAT][params->comps - 1],
     758                 :            :               lin_lut, lin_lut, lin_lut, lin_lut,
     759                 :            :               lin_lut, lin_lut, lin_lut, lin_lut);
     760                 :            :     }
     761                 :            : 
     762         [ +  + ]:        627 :     if (method == SH_LUT_TETRAHEDRAL) {
     763                 :            :         ident_t int_lut = name;
     764                 :         65 :         name = sh_fresh(sh, "lut_barycentric");
     765                 :         65 :         GLSLH("%s "$"(vec3 pos) {                                       \n"
     766                 :            :               // Compute bounding vertices and fractional part
     767                 :            :               "    pos = clamp(pos, 0.0, 1.0) * vec3(%d.0, %d.0, %d.0); \n"
     768                 :            :               "    vec3 base = floor(pos);                              \n"
     769                 :            :               "    vec3 fpart = pos - base;                             \n"
     770                 :            :               // v0 and v3 are always 'black' and 'white', respectively
     771                 :            :               // v1 and v2 are the closest RGB and CMY vertices, respectively
     772                 :            :               "    ivec3 v0 = ivec3(base), v3 = ivec3(ceil(pos));       \n"
     773                 :            :               "    ivec3 v1 = v0, v2 = v3;                              \n"
     774                 :            :               // Table of boolean checks to simplify following math
     775                 :            :               "    bvec3 c = greaterThanEqual(fpart.xyz, fpart.yzx);    \n"
     776                 :            :               "    bool c_xy = c.x, c_yx = !c.x,                        \n"
     777                 :            :               "       c_yz = c.y, c_zy = !c.y,                          \n"
     778                 :            :               "       c_zx = c.z, c_xz = !c.z;                          \n"
     779                 :            :               "    vec3 s = fpart.xyz;                                  \n"
     780                 :            :               "    bool cond;                                           \n",
     781                 :            :               vartypes[PL_VAR_FLOAT][params->comps - 1], name,
     782                 :            :               sizes[0] - 1, sizes[1] - 1, sizes[2] - 1);
     783                 :            : 
     784                 :            :         // Subdivision of the cube into six congruent tetrahedras
     785                 :            :         //
     786                 :            :         // For each tetrahedron, test if the point is inside, and if so, update
     787                 :            :         // the edge vertices. We test all six, even though only one case will
     788                 :            :         // ever be true, because this avoids branches.
     789                 :            :         static const char *indices[] = { "xyz", "xzy", "zxy", "zyx", "yzx", "yxz"};
     790         [ +  + ]:        455 :         for (int i = 0; i < PL_ARRAY_SIZE(indices); i++) {
     791                 :        390 :             const char x = indices[i][0], y = indices[i][1], z = indices[i][2];
     792                 :        390 :             GLSLH("cond = c_%c%c && c_%c%c;          \n"
     793                 :            :                   "s = cond ? fpart.%c%c%c : s;      \n"
     794                 :            :                   "v1.%c = cond ? v3.%c : v1.%c;     \n"
     795                 :            :                   "v2.%c = cond ? v0.%c : v2.%c;     \n",
     796                 :            :                   x, y, y, z,
     797                 :            :                   x, y, z,
     798                 :            :                   x, x, x,
     799                 :            :                   z, z, z);
     800                 :            :         }
     801                 :            : 
     802                 :            :         // Interpolate in barycentric coordinates, with four texel fetches
     803                 :         65 :         GLSLH("    return (1.0 - s.x) * "$"(v0) +   \n"
     804                 :            :               "           (s.x - s.y) * "$"(v1) +   \n"
     805                 :            :               "           (s.y - s.z) * "$"(v2) +   \n"
     806                 :            :               "           (s.z)       * "$"(v3);    \n"
     807                 :            :               "}                                    \n",
     808                 :            :               int_lut, int_lut, int_lut, int_lut);
     809                 :            :     }
     810                 :            : 
     811         [ -  + ]:        627 :     lut->error = false;
     812                 :            :     pl_cache_obj_free(&obj);
     813         [ -  + ]:        627 :     pl_assert(name);
     814                 :            :     return name;
     815                 :            : 
     816                 :          1 : error:
     817         [ -  + ]:          1 :     lut->error = true;
     818                 :            :     pl_cache_obj_free(&obj);
     819                 :          1 :     return NULL_IDENT;
     820                 :            : }

Generated by: LCOV version 1.16