LCOV - code coverage report
Current view: top level - src/shaders - custom_mpv.c (source / functions) Hit Total Coverage
Test: Code coverage Lines: 602 809 74.4 %
Date: 2025-03-29 09:04:10 Functions: 19 19 100.0 %
Legend: Lines: hit not hit | Branches: + taken - not taken # not executed Branches: 372 659 56.4 %

           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 <limits.h>
      20                 :            : 
      21                 :            : #include "gpu.h"
      22                 :            : #include "shaders.h"
      23                 :            : 
      24                 :            : #include <libplacebo/shaders/colorspace.h>
      25                 :            : #include <libplacebo/shaders/custom.h>
      26                 :            : 
      27                 :            : // Hard-coded size limits, mainly for convenience (to avoid dynamic memory)
      28                 :            : #define SHADER_MAX_HOOKS 16
      29                 :            : #define SHADER_MAX_BINDS 16
      30                 :            : #define MAX_SHEXP_SIZE 32
      31                 :            : 
      32                 :            : enum shexp_op {
      33                 :            :     SHEXP_OP_ADD,
      34                 :            :     SHEXP_OP_SUB,
      35                 :            :     SHEXP_OP_MUL,
      36                 :            :     SHEXP_OP_DIV,
      37                 :            :     SHEXP_OP_MOD,
      38                 :            :     SHEXP_OP_NOT,
      39                 :            :     SHEXP_OP_GT,
      40                 :            :     SHEXP_OP_LT,
      41                 :            :     SHEXP_OP_EQ,
      42                 :            : };
      43                 :            : 
      44                 :            : enum shexp_tag {
      45                 :            :     SHEXP_END = 0, // End of an RPN expression
      46                 :            :     SHEXP_CONST, // Push a constant value onto the stack
      47                 :            :     SHEXP_TEX_W, // Get the width/height of a named texture (variable)
      48                 :            :     SHEXP_TEX_H,
      49                 :            :     SHEXP_OP2, // Pop two elements and push the result of a dyadic operation
      50                 :            :     SHEXP_OP1, // Pop one element and push the result of a monadic operation
      51                 :            :     SHEXP_VAR, // Arbitrary variable (e.g. shader parameters)
      52                 :            : };
      53                 :            : 
      54                 :            : struct shexp {
      55                 :            :     enum shexp_tag tag;
      56                 :            :     union {
      57                 :            :         float cval;
      58                 :            :         pl_str varname;
      59                 :            :         enum shexp_op op;
      60                 :            :     } val;
      61                 :            : };
      62                 :            : 
      63                 :            : struct custom_shader_hook {
      64                 :            :     // Variable/literal names of textures
      65                 :            :     pl_str pass_desc;
      66                 :            :     pl_str hook_tex[SHADER_MAX_HOOKS];
      67                 :            :     pl_str bind_tex[SHADER_MAX_BINDS];
      68                 :            :     pl_str save_tex;
      69                 :            : 
      70                 :            :     // Shader body itself + metadata
      71                 :            :     pl_str pass_body;
      72                 :            :     float offset[2];
      73                 :            :     bool offset_align;
      74                 :            :     int comps;
      75                 :            : 
      76                 :            :     // Special expressions governing the output size and execution conditions
      77                 :            :     struct shexp width[MAX_SHEXP_SIZE];
      78                 :            :     struct shexp height[MAX_SHEXP_SIZE];
      79                 :            :     struct shexp cond[MAX_SHEXP_SIZE];
      80                 :            : 
      81                 :            :     // Special metadata for compute shaders
      82                 :            :     bool is_compute;
      83                 :            :     int block_w, block_h;       // Block size (each block corresponds to one WG)
      84                 :            :     int threads_w, threads_h;   // How many threads form a WG
      85                 :            : };
      86                 :            : 
      87                 :         24 : static bool parse_rpn_shexpr(pl_str line, struct shexp out[MAX_SHEXP_SIZE])
      88                 :            : {
      89                 :            :     int pos = 0;
      90                 :            : 
      91         [ +  + ]:        120 :     while (line.len > 0) {
      92                 :         96 :         pl_str word = pl_str_split_char(line, ' ', &line);
      93         [ +  + ]:         96 :         if (word.len == 0)
      94                 :         84 :             continue;
      95                 :            : 
      96         [ +  - ]:         72 :         if (pos >= MAX_SHEXP_SIZE)
      97                 :          0 :             return false;
      98                 :            : 
      99                 :         72 :         struct shexp *exp = &out[pos++];
     100                 :            : 
     101   [ +  +  -  + ]:         72 :         if (pl_str_eatend0(&word, ".w") || pl_str_eatend0(&word, ".width")) {
     102                 :         12 :             exp->tag = SHEXP_TEX_W;
     103                 :         12 :             exp->val.varname = word;
     104                 :         12 :             continue;
     105                 :            :         }
     106                 :            : 
     107   [ +  +  -  + ]:         60 :         if (pl_str_eatend0(&word, ".h") || pl_str_eatend0(&word, ".height")) {
     108                 :          4 :             exp->tag = SHEXP_TEX_H;
     109                 :          4 :             exp->val.varname = word;
     110                 :          4 :             continue;
     111                 :            :         }
     112                 :            : 
     113   [ -  -  +  -  :         56 :         switch (word.buf[0]) {
          -  -  +  +  +  
                      + ]
     114                 :          0 :         case '+': exp->tag = SHEXP_OP2; exp->val.op = SHEXP_OP_ADD; continue;
     115                 :          0 :         case '-': exp->tag = SHEXP_OP2; exp->val.op = SHEXP_OP_SUB; continue;
     116                 :          8 :         case '*': exp->tag = SHEXP_OP2; exp->val.op = SHEXP_OP_MUL; continue;
     117                 :          0 :         case '/': exp->tag = SHEXP_OP2; exp->val.op = SHEXP_OP_DIV; continue;
     118                 :          0 :         case '%': exp->tag = SHEXP_OP2; exp->val.op = SHEXP_OP_MOD; continue;
     119                 :          0 :         case '!': exp->tag = SHEXP_OP1; exp->val.op = SHEXP_OP_NOT; continue;
     120                 :          4 :         case '>': exp->tag = SHEXP_OP2; exp->val.op = SHEXP_OP_GT;  continue;
     121                 :          8 :         case '<': exp->tag = SHEXP_OP2; exp->val.op = SHEXP_OP_LT;  continue;
     122                 :          4 :         case '=': exp->tag = SHEXP_OP2; exp->val.op = SHEXP_OP_EQ;  continue;
     123                 :            :         }
     124                 :            : 
     125         [ +  + ]:         32 :         if (word.buf[0] >= '0' && word.buf[0] <= '9') {
     126                 :         20 :             exp->tag = SHEXP_CONST;
     127         [ +  - ]:         20 :             if (!pl_str_parse_float(word, &exp->val.cval))
     128                 :            :                 return false;
     129                 :         20 :             continue;
     130                 :            :         }
     131                 :            : 
     132                 :            :         // Treat as generic variable
     133                 :         12 :         exp->tag = SHEXP_VAR;
     134                 :         12 :         exp->val.varname = word;
     135                 :            :     }
     136                 :            : 
     137                 :            :     return true;
     138                 :            : }
     139                 :            : 
     140                 :         48 : static inline pl_str split_magic(pl_str *body)
     141                 :            : {
     142                 :         48 :     pl_str ret = pl_str_split_str0(*body, "//!", body);
     143         [ +  + ]:         48 :     if (body->len) {
     144                 :            :         // Make sure the separator is included in the remainder
     145                 :         38 :         body->buf -= 3;
     146                 :         38 :         body->len += 3;
     147                 :            :     }
     148                 :            : 
     149                 :         48 :     return ret;
     150                 :            : }
     151                 :            : 
     152                 :         22 : static bool parse_hook(pl_log log, pl_str *body, struct custom_shader_hook *out)
     153                 :            : {
     154                 :         22 :     *out = (struct custom_shader_hook){
     155                 :            :         .pass_desc = pl_str0("unknown user shader"),
     156                 :            :         .width = {{ SHEXP_TEX_W, { .varname = pl_str0("HOOKED") }}},
     157                 :            :         .height = {{ SHEXP_TEX_H, { .varname = pl_str0("HOOKED") }}},
     158                 :            :         .cond = {{ SHEXP_CONST, { .cval = 1.0 }}},
     159                 :            :     };
     160                 :            : 
     161                 :            :     int hook_idx = 0;
     162                 :            :     int bind_idx = 0;
     163                 :            : 
     164                 :            :     // Parse all headers
     165                 :         92 :     while (true) {
     166                 :            :         pl_str rest;
     167                 :        114 :         pl_str line = pl_str_strip(pl_str_getline(*body, &rest));
     168                 :            : 
     169                 :            :         // Check for the presence of the magic line beginning
     170         [ +  + ]:        114 :         if (!pl_str_eatstart0(&line, "//!"))
     171                 :            :             break;
     172                 :            : 
     173                 :         92 :         *body = rest;
     174                 :            : 
     175                 :            :         // Parse the supported commands
     176         [ +  + ]:         92 :         if (pl_str_eatstart0(&line, "HOOK")) {
     177         [ -  + ]:         22 :             if (hook_idx == SHADER_MAX_HOOKS) {
     178                 :          0 :                 pl_err(log, "Passes may only hook up to %d textures!",
     179                 :            :                        SHADER_MAX_HOOKS);
     180                 :          0 :                 return false;
     181                 :            :             }
     182                 :         22 :             out->hook_tex[hook_idx++] = pl_str_strip(line);
     183                 :         92 :             continue;
     184                 :            :         }
     185                 :            : 
     186         [ +  + ]:         70 :         if (pl_str_eatstart0(&line, "BIND")) {
     187         [ -  + ]:         22 :             if (bind_idx == SHADER_MAX_BINDS) {
     188                 :          0 :                 pl_err(log, "Passes may only bind up to %d textures!",
     189                 :            :                        SHADER_MAX_BINDS);
     190                 :          0 :                 return false;
     191                 :            :             }
     192                 :         22 :             out->bind_tex[bind_idx++] = pl_str_strip(line);
     193                 :         22 :             continue;
     194                 :            :         }
     195                 :            : 
     196         [ +  + ]:         52 :         if (pl_str_eatstart0(&line, "SAVE")) {
     197                 :          4 :             pl_str save_tex = pl_str_strip(line);
     198         [ -  + ]:          4 :             if (pl_str_equals0(save_tex, "HOOKED")) {
     199                 :            :                 // This is a special name that means "overwrite existing"
     200                 :            :                 // texture, which we just signal by not having any `save_tex`
     201                 :            :                 // name set.
     202                 :          0 :                 out->save_tex = (pl_str) {0};
     203         [ -  + ]:          4 :             } else if (pl_str_equals0(save_tex, "MAIN")) {
     204                 :            :                 // Compatibility alias
     205                 :          0 :                 out->save_tex = pl_str0("MAINPRESUB");
     206                 :            :             } else {
     207                 :          4 :                 out->save_tex = save_tex;
     208                 :            :             };
     209                 :            :             continue;
     210                 :            :         }
     211                 :            : 
     212         [ +  + ]:         44 :         if (pl_str_eatstart0(&line, "DESC")) {
     213                 :         14 :             out->pass_desc = pl_str_strip(line);
     214                 :         14 :             continue;
     215                 :            :         }
     216                 :            : 
     217         [ -  + ]:         30 :         if (pl_str_eatstart0(&line, "OFFSET")) {
     218                 :          0 :             line = pl_str_strip(line);
     219         [ #  # ]:          0 :             if (pl_str_equals0(line, "ALIGN")) {
     220                 :          0 :                 out->offset_align = true;
     221                 :            :             } else {
     222   [ #  #  #  # ]:          0 :                 if (!pl_str_parse_float(pl_str_split_char(line, ' ', &line), &out->offset[0]) ||
     223                 :          0 :                     !pl_str_parse_float(pl_str_split_char(line, ' ', &line), &out->offset[1]) ||
     224         [ #  # ]:          0 :                     line.len)
     225                 :            :                 {
     226                 :          0 :                     pl_err(log, "Error while parsing OFFSET!");
     227                 :          0 :                     return false;
     228                 :            :                 }
     229                 :            :             }
     230                 :          0 :             continue;
     231                 :            :         }
     232                 :            : 
     233         [ +  + ]:         30 :         if (pl_str_eatstart0(&line, "WIDTH")) {
     234         [ -  + ]:          4 :             if (!parse_rpn_shexpr(line, out->width)) {
     235                 :          0 :                 pl_err(log, "Error while parsing WIDTH!");
     236                 :          0 :                 return false;
     237                 :            :             }
     238                 :          4 :             continue;
     239                 :            :         }
     240                 :            : 
     241         [ +  + ]:         26 :         if (pl_str_eatstart0(&line, "HEIGHT")) {
     242         [ -  + ]:          4 :             if (!parse_rpn_shexpr(line, out->height)) {
     243                 :          0 :                 pl_err(log, "Error while parsing HEIGHT!");
     244                 :          0 :                 return false;
     245                 :            :             }
     246                 :          4 :             continue;
     247                 :            :         }
     248                 :            : 
     249         [ +  + ]:         22 :         if (pl_str_eatstart0(&line, "WHEN")) {
     250         [ -  + ]:         16 :             if (!parse_rpn_shexpr(line, out->cond)) {
     251                 :          0 :                 pl_err(log, "Error while parsing WHEN!");
     252                 :          0 :                 return false;
     253                 :            :             }
     254                 :         16 :             continue;
     255                 :            :         }
     256                 :            : 
     257         [ +  - ]:          6 :         if (pl_str_eatstart0(&line, "COMPONENTS")) {
     258         [ -  + ]:          6 :             if (!pl_str_parse_int(pl_str_strip(line), &out->comps)) {
     259         [ #  # ]:          0 :                 pl_err(log, "Error parsing COMPONENTS: '%.*s'", PL_STR_FMT(line));
     260                 :          0 :                 return false;
     261                 :            :             }
     262                 :          6 :             continue;
     263                 :            :         }
     264                 :            : 
     265         [ #  # ]:          0 :         if (pl_str_eatstart0(&line, "COMPUTE")) {
     266                 :          0 :             line = pl_str_strip(line);
     267   [ #  #  #  # ]:          0 :             bool ok = pl_str_parse_int(pl_str_split_char(line, ' ', &line), &out->block_w) &&
     268                 :          0 :                       pl_str_parse_int(pl_str_split_char(line, ' ', &line), &out->block_h);
     269                 :            : 
     270                 :          0 :             line = pl_str_strip(line);
     271   [ #  #  #  # ]:          0 :             if (ok && line.len) {
     272         [ #  # ]:          0 :                 ok = pl_str_parse_int(pl_str_split_char(line, ' ', &line), &out->threads_w) &&
     273         [ #  # ]:          0 :                      pl_str_parse_int(pl_str_split_char(line, ' ', &line), &out->threads_h) &&
     274         [ #  # ]:          0 :                      !line.len;
     275                 :            :             } else {
     276                 :          0 :                 out->threads_w = out->block_w;
     277                 :          0 :                 out->threads_h = out->block_h;
     278                 :            :             }
     279                 :            : 
     280         [ #  # ]:          0 :             if (!ok) {
     281                 :          0 :                 pl_err(log, "Error while parsing COMPUTE!");
     282                 :          0 :                 return false;
     283                 :            :             }
     284                 :            : 
     285                 :          0 :             out->is_compute = true;
     286                 :          0 :             continue;
     287                 :            :         }
     288                 :            : 
     289                 :            :         // Unknown command type
     290         [ #  # ]:          0 :         pl_err(log, "Unrecognized command '%.*s'!", PL_STR_FMT(line));
     291                 :          0 :         return false;
     292                 :            :     }
     293                 :            : 
     294                 :            :     // The rest of the file up until the next magic line beginning (if any)
     295                 :            :     // shall be the shader body
     296                 :         22 :     out->pass_body = split_magic(body);
     297                 :            : 
     298                 :            :     // Sanity checking
     299         [ -  + ]:         22 :     if (hook_idx == 0)
     300                 :          0 :         pl_warn(log, "Pass has no hooked textures (will be ignored)!");
     301                 :            : 
     302                 :            :     return true;
     303                 :            : }
     304                 :            : 
     305                 :          6 : static bool parse_tex(pl_gpu gpu, void *alloc, pl_str *body,
     306                 :            :                       struct pl_shader_desc *out)
     307                 :            : {
     308                 :          6 :     *out = (struct pl_shader_desc) {
     309                 :            :         .desc = {
     310                 :            :             .name = "USER_TEX",
     311                 :            :             .type = PL_DESC_SAMPLED_TEX,
     312                 :            :         },
     313                 :            :     };
     314                 :            : 
     315                 :          6 :     struct pl_tex_params params = {
     316                 :            :         .w = 1, .h = 1, .d = 0,
     317                 :            :         .sampleable = true,
     318                 :            :         .debug_tag = PL_DEBUG_TAG,
     319                 :            :     };
     320                 :            : 
     321                 :         28 :     while (true) {
     322                 :            :         pl_str rest;
     323                 :         34 :         pl_str line = pl_str_strip(pl_str_getline(*body, &rest));
     324                 :            : 
     325         [ +  + ]:         34 :         if (!pl_str_eatstart0(&line, "//!"))
     326                 :            :             break;
     327                 :            : 
     328                 :         28 :         *body = rest;
     329                 :            : 
     330         [ +  + ]:         28 :         if (pl_str_eatstart0(&line, "TEXTURE")) {
     331                 :          6 :             out->desc.name = pl_strdup0(alloc, pl_str_strip(line));
     332                 :         28 :             continue;
     333                 :            :         }
     334                 :            : 
     335         [ +  + ]:         22 :         if (pl_str_eatstart0(&line, "SIZE")) {
     336                 :          6 :             line = pl_str_strip(line);
     337                 :            :             int dims = 0;
     338                 :            :             int dim[4]; // extra space to catch invalid extra entries
     339   [ +  +  +  - ]:         18 :             while (line.len && dims < PL_ARRAY_SIZE(dim)) {
     340         [ -  + ]:         12 :                 if (!pl_str_parse_int(pl_str_split_char(line, ' ', &line), &dim[dims++])) {
     341                 :          0 :                     PL_ERR(gpu, "Error while parsing SIZE!");
     342                 :          0 :                     return false;
     343                 :            :                 }
     344                 :            :             }
     345                 :            : 
     346                 :            :             uint32_t lim = dims == 1 ? gpu->limits.max_tex_1d_dim
     347         [ -  + ]:          6 :                          : dims == 2 ? gpu->limits.max_tex_2d_dim
     348         [ +  - ]:          6 :                          : dims == 3 ? gpu->limits.max_tex_3d_dim
     349         [ #  # ]:          0 :                          : 0;
     350                 :            : 
     351                 :            :             // Sanity check against GPU size limits
     352   [ -  +  -  - ]:          6 :             switch (dims) {
     353                 :          0 :             case 3:
     354                 :          0 :                 params.d = dim[2];
     355   [ #  #  #  # ]:          0 :                 if (params.d < 1 || params.d > lim) {
     356                 :          0 :                     PL_ERR(gpu, "SIZE %d exceeds GPU's texture size limits (%d)!",
     357                 :            :                            params.d, lim);
     358                 :          0 :                     return false;
     359                 :            :                 }
     360                 :            :                 // fall through
     361                 :            :             case 2:
     362                 :          6 :                 params.h = dim[1];
     363   [ +  -  -  + ]:          6 :                 if (params.h < 1 || params.h > lim) {
     364                 :          0 :                     PL_ERR(gpu, "SIZE %d exceeds GPU's texture size limits (%d)!",
     365                 :            :                            params.h, lim);
     366                 :          0 :                     return false;
     367                 :            :                 }
     368                 :            :                 // fall through
     369                 :            :             case 1:
     370                 :          6 :                 params.w = dim[0];
     371   [ +  -  -  + ]:          6 :                 if (params.w < 1 || params.w > lim) {
     372                 :          0 :                     PL_ERR(gpu, "SIZE %d exceeds GPU's texture size limits (%d)!",
     373                 :            :                            params.w, lim);
     374                 :          0 :                     return false;
     375                 :            :                 }
     376                 :            :                 break;
     377                 :            : 
     378                 :          0 :             default:
     379                 :          0 :                 PL_ERR(gpu, "Invalid number of texture dimensions!");
     380                 :          0 :                 return false;
     381                 :            :             };
     382                 :            : 
     383                 :            :             // Clear out the superfluous components
     384         [ +  - ]:          6 :             if (dims < 3)
     385                 :          6 :                 params.d = 0;
     386         [ -  + ]:          6 :             if (dims < 2)
     387                 :          0 :                 params.h = 0;
     388                 :          6 :             continue;
     389                 :            :         }
     390                 :            : 
     391         [ +  + ]:         16 :         if (pl_str_eatstart0(&line, "FORMAT")) {
     392                 :          6 :             line = pl_str_strip(line);
     393                 :          6 :             params.format = NULL;
     394         [ +  - ]:         44 :             for (int n = 0; n < gpu->num_formats; n++) {
     395                 :         44 :                 pl_fmt fmt = gpu->formats[n];
     396         [ +  + ]:         44 :                 if (pl_str_equals0(line, fmt->name)) {
     397                 :          6 :                     params.format = fmt;
     398                 :          6 :                     break;
     399                 :            :                 }
     400                 :            :             }
     401                 :            : 
     402   [ +  -  -  + ]:          6 :             if (!params.format || params.format->opaque) {
     403         [ #  # ]:          0 :                 PL_ERR(gpu, "Unrecognized/unavailable FORMAT name: '%.*s'!",
     404                 :            :                        PL_STR_FMT(line));
     405                 :          0 :                 return false;
     406                 :            :             }
     407                 :            : 
     408         [ -  + ]:          6 :             if (!(params.format->caps & PL_FMT_CAP_SAMPLEABLE)) {
     409         [ #  # ]:          0 :                 PL_ERR(gpu, "Chosen FORMAT '%.*s' is not sampleable!",
     410                 :            :                        PL_STR_FMT(line));
     411                 :          0 :                 return false;
     412                 :            :             }
     413                 :          6 :             continue;
     414                 :            :         }
     415                 :            : 
     416         [ +  + ]:         10 :         if (pl_str_eatstart0(&line, "FILTER")) {
     417                 :          4 :             line = pl_str_strip(line);
     418         [ -  + ]:          4 :             if (pl_str_equals0(line, "LINEAR")) {
     419                 :          0 :                 out->binding.sample_mode = PL_TEX_SAMPLE_LINEAR;
     420         [ +  - ]:          4 :             } else if (pl_str_equals0(line, "NEAREST")) {
     421                 :          4 :                 out->binding.sample_mode = PL_TEX_SAMPLE_NEAREST;
     422                 :            :             } else {
     423         [ #  # ]:          0 :                 PL_ERR(gpu, "Unrecognized FILTER: '%.*s'!", PL_STR_FMT(line));
     424                 :          0 :                 return false;
     425                 :            :             }
     426                 :          4 :             continue;
     427                 :            :         }
     428                 :            : 
     429         [ +  + ]:          6 :         if (pl_str_eatstart0(&line, "BORDER")) {
     430                 :          4 :             line = pl_str_strip(line);
     431         [ -  + ]:          4 :             if (pl_str_equals0(line, "CLAMP")) {
     432                 :          0 :                 out->binding.address_mode = PL_TEX_ADDRESS_CLAMP;
     433         [ +  - ]:          4 :             } else if (pl_str_equals0(line, "REPEAT")) {
     434                 :          4 :                 out->binding.address_mode = PL_TEX_ADDRESS_REPEAT;
     435         [ #  # ]:          0 :             } else if (pl_str_equals0(line, "MIRROR")) {
     436                 :          0 :                 out->binding.address_mode = PL_TEX_ADDRESS_MIRROR;
     437                 :            :             } else {
     438         [ #  # ]:          0 :                 PL_ERR(gpu, "Unrecognized BORDER: '%.*s'!", PL_STR_FMT(line));
     439                 :          0 :                 return false;
     440                 :            :             }
     441                 :          4 :             continue;
     442                 :            :         }
     443                 :            : 
     444         [ +  - ]:          2 :         if (pl_str_eatstart0(&line, "STORAGE")) {
     445                 :          2 :             params.storable = true;
     446                 :          2 :             out->desc.type = PL_DESC_STORAGE_IMG;
     447                 :          2 :             out->desc.access = PL_DESC_ACCESS_READWRITE;
     448                 :          2 :             out->memory = PL_MEMORY_COHERENT;
     449                 :          2 :             continue;
     450                 :            :         }
     451                 :            : 
     452         [ #  # ]:          0 :         PL_ERR(gpu, "Unrecognized command '%.*s'!", PL_STR_FMT(line));
     453                 :          0 :         return false;
     454                 :            :     }
     455                 :            : 
     456         [ -  + ]:          6 :     if (!params.format) {
     457                 :          0 :         PL_ERR(gpu, "No FORMAT specified!");
     458                 :          0 :         return false;
     459                 :            :     }
     460                 :            : 
     461                 :          6 :     int caps = params.format->caps;
     462   [ -  +  -  - ]:          6 :     if (out->binding.sample_mode == PL_TEX_SAMPLE_LINEAR && !(caps & PL_FMT_CAP_LINEAR)) {
     463                 :          0 :         PL_ERR(gpu, "The specified texture format cannot be linear filtered!");
     464                 :          0 :         return false;
     465                 :            :     }
     466                 :            : 
     467                 :            :     // Decode the rest of the section (up to the next //! marker) as raw hex
     468                 :            :     // data for the texture
     469                 :          6 :     pl_str tex, hexdata = split_magic(body);
     470         [ -  + ]:          6 :     if (!pl_str_decode_hex(NULL, pl_str_strip(hexdata), &tex)) {
     471                 :          0 :         PL_ERR(gpu, "Error while parsing TEXTURE body: must be a valid "
     472                 :            :                     "hexadecimal sequence!");
     473                 :          0 :         return false;
     474                 :            :     }
     475                 :            : 
     476   [ +  -  -  + ]:         12 :     int texels = params.w * PL_DEF(params.h, 1) * PL_DEF(params.d, 1);
     477                 :          6 :     size_t expected_len = texels * params.format->texel_size;
     478   [ +  +  +  - ]:          6 :     if (tex.len == 0 && params.storable) {
     479                 :            :         // In this case, it's okay that the texture has no initial data
     480                 :          2 :         pl_free_ptr(&tex.buf);
     481         [ -  + ]:          4 :     } else if (tex.len != expected_len) {
     482                 :          0 :         PL_ERR(gpu, "Shader TEXTURE size mismatch: got %zu bytes, expected %zu!",
     483                 :            :                tex.len, expected_len);
     484                 :          0 :         pl_free(tex.buf);
     485                 :          0 :         return false;
     486                 :            :     }
     487                 :            : 
     488                 :          6 :     params.initial_data = tex.buf;
     489                 :          6 :     out->binding.object = pl_tex_create(gpu, &params);
     490                 :          6 :     pl_free(tex.buf);
     491                 :            : 
     492         [ -  + ]:          6 :     if (!out->binding.object) {
     493                 :          0 :         PL_ERR(gpu, "Failed creating custom texture!");
     494                 :          0 :         return false;
     495                 :            :     }
     496                 :            : 
     497                 :            :     return true;
     498                 :            : }
     499                 :            : 
     500                 :          4 : static bool parse_buf(pl_gpu gpu, void *alloc, pl_str *body,
     501                 :            :                       struct pl_shader_desc *out)
     502                 :            : {
     503                 :          4 :     *out = (struct pl_shader_desc) {
     504                 :            :         .desc = {
     505                 :            :             .name = "USER_BUF",
     506                 :            :             .type = PL_DESC_BUF_UNIFORM,
     507                 :            :         },
     508                 :            :     };
     509                 :            : 
     510                 :            :     // Temporary, to allow deferring variable placement until all headers
     511                 :            :     // have been processed (in order to e.g. determine buffer type)
     512                 :          4 :     void *tmp = pl_tmp(alloc); // will be freed automatically on failure
     513                 :            :     PL_ARRAY(struct pl_var) vars = {0};
     514                 :            : 
     515                 :         14 :     while (true) {
     516                 :            :         pl_str rest;
     517                 :         18 :         pl_str line = pl_str_strip(pl_str_getline(*body, &rest));
     518                 :            : 
     519         [ +  + ]:         18 :         if (!pl_str_eatstart0(&line, "//!"))
     520                 :            :             break;
     521                 :            : 
     522                 :         14 :         *body = rest;
     523                 :            : 
     524         [ +  + ]:         14 :         if (pl_str_eatstart0(&line, "BUFFER")) {
     525                 :          4 :             out->desc.name = pl_strdup0(alloc, pl_str_strip(line));
     526                 :         18 :             continue;
     527                 :            :         }
     528                 :            : 
     529         [ +  + ]:         10 :         if (pl_str_eatstart0(&line, "STORAGE")) {
     530                 :          2 :             out->desc.type = PL_DESC_BUF_STORAGE;
     531                 :          2 :             out->desc.access = PL_DESC_ACCESS_READWRITE;
     532                 :          2 :             out->memory = PL_MEMORY_COHERENT;
     533                 :          2 :             continue;
     534                 :            :         }
     535                 :            : 
     536         [ +  - ]:          8 :         if (pl_str_eatstart0(&line, "VAR")) {
     537                 :          8 :             pl_str type_name = pl_str_split_char(pl_str_strip(line), ' ', &line);
     538                 :          8 :             struct pl_var var = {0};
     539         [ +  - ]:         34 :             for (const struct pl_named_var *nv = pl_var_glsl_types; nv->glsl_name; nv++) {
     540         [ +  + ]:         34 :                 if (pl_str_equals0(type_name, nv->glsl_name)) {
     541                 :          8 :                     var = nv->var;
     542                 :          8 :                     break;
     543                 :            :                 }
     544                 :            :             }
     545                 :            : 
     546         [ -  + ]:          8 :             if (!var.type) {
     547                 :            :                 // No type found
     548         [ #  # ]:          0 :                 PL_ERR(gpu, "Unrecognized GLSL type '%.*s'!", PL_STR_FMT(type_name));
     549                 :          0 :                 return false;
     550                 :            :             }
     551                 :            : 
     552                 :          8 :             pl_str var_name = pl_str_split_char(line, '[', &line);
     553         [ +  + ]:          8 :             if (line.len > 0) {
     554                 :            :                 // Parse array dimension
     555         [ -  + ]:          2 :                 if (!pl_str_parse_int(pl_str_split_char(line, ']', NULL), &var.dim_a)) {
     556         [ #  # ]:          0 :                     PL_ERR(gpu, "Failed parsing array dimension from [%.*s!",
     557                 :            :                            PL_STR_FMT(line));
     558                 :          0 :                     return false;
     559                 :            :                 }
     560                 :            : 
     561         [ -  + ]:          2 :                 if (var.dim_a < 1) {
     562                 :          0 :                     PL_ERR(gpu, "Invalid array dimension %d!", var.dim_a);
     563                 :          0 :                     return false;
     564                 :            :                 }
     565                 :            :             }
     566                 :            : 
     567                 :          8 :             var.name = pl_strdup0(alloc, pl_str_strip(var_name));
     568   [ +  +  -  +  :          8 :             PL_ARRAY_APPEND(tmp, vars, var);
                   -  + ]
     569                 :          8 :             continue;
     570                 :            :         }
     571                 :            : 
     572         [ #  # ]:          0 :         PL_ERR(gpu, "Unrecognized command '%.*s'!", PL_STR_FMT(line));
     573                 :          0 :         return false;
     574                 :            :     }
     575                 :            : 
     576                 :            :     // Try placing all of the buffer variables
     577         [ +  + ]:         12 :     for (int i = 0; i < vars.num; i++) {
     578         [ -  + ]:          8 :         if (!sh_buf_desc_append(alloc, gpu, out, NULL, vars.elem[i])) {
     579                 :          0 :             PL_ERR(gpu, "Custom buffer exceeds GPU limitations!");
     580                 :          0 :             return false;
     581                 :            :         }
     582                 :            :     }
     583                 :            : 
     584                 :            :     // Decode the rest of the section (up to the next //! marker) as raw hex
     585                 :            :     // data for the buffer
     586                 :          4 :     pl_str data, hexdata = split_magic(body);
     587         [ -  + ]:          4 :     if (!pl_str_decode_hex(tmp, pl_str_strip(hexdata), &data)) {
     588                 :          0 :         PL_ERR(gpu, "Error while parsing BUFFER body: must be a valid "
     589                 :            :                     "hexadecimal sequence!");
     590                 :          0 :         return false;
     591                 :            :     }
     592                 :            : 
     593                 :          4 :     size_t buf_size = sh_buf_desc_size(out);
     594   [ +  +  -  + ]:          4 :     if (data.len == 0 && out->desc.type == PL_DESC_BUF_STORAGE) {
     595                 :            :         // In this case, it's okay that the buffer has no initial data
     596         [ -  + ]:          2 :     } else if (data.len != buf_size) {
     597                 :          0 :         PL_ERR(gpu, "Shader BUFFER size mismatch: got %zu bytes, expected %zu!",
     598                 :            :                data.len, buf_size);
     599                 :          0 :         return false;
     600                 :            :     }
     601                 :            : 
     602         [ +  + ]:          4 :     out->binding.object = pl_buf_create(gpu, pl_buf_params(
     603                 :            :         .size = buf_size,
     604                 :            :         .uniform = out->desc.type == PL_DESC_BUF_UNIFORM,
     605                 :            :         .storable = out->desc.type == PL_DESC_BUF_STORAGE,
     606                 :            :         .initial_data = data.len ? data.buf : NULL,
     607                 :            :     ));
     608                 :            : 
     609         [ -  + ]:          4 :     if (!out->binding.object) {
     610                 :          0 :         PL_ERR(gpu, "Failed creating custom buffer!");
     611                 :          0 :         return false;
     612                 :            :     }
     613                 :            : 
     614                 :          4 :     pl_free(tmp);
     615                 :          4 :     return true;
     616                 :            : }
     617                 :            : 
     618                 :         36 : static bool parse_var(pl_log log, pl_str str, enum pl_var_type type, pl_var_data *out)
     619                 :            : {
     620         [ +  + ]:         36 :     if (!str.len)
     621                 :            :         return true;
     622                 :            : 
     623                 :         24 :     pl_str buf = str;
     624                 :            :     bool ok = false;
     625   [ +  +  +  -  :         24 :     switch (type) {
                      - ]
     626                 :          4 :     case PL_VAR_SINT:
     627                 :          4 :         ok = pl_str_parse_int(pl_str_split_char(buf, ' ', &buf), &out->i);
     628                 :          4 :         break;
     629                 :          8 :     case PL_VAR_UINT:
     630                 :          8 :         ok = pl_str_parse_uint(pl_str_split_char(buf, ' ', &buf), &out->u);
     631                 :          8 :         break;
     632                 :         12 :     case PL_VAR_FLOAT:
     633                 :         12 :         ok = pl_str_parse_float(pl_str_split_char(buf, ' ', &buf), &out->f);
     634                 :         12 :         break;
     635                 :            :     case PL_VAR_INVALID:
     636                 :            :     case PL_VAR_TYPE_COUNT:
     637                 :          0 :         pl_unreachable();
     638                 :            :     }
     639                 :            : 
     640         [ +  - ]:         24 :     if (pl_str_strip(buf).len > 0)
     641                 :            :         ok = false; // left-over garbage
     642                 :            : 
     643         [ +  - ]:         24 :     if (!ok) {
     644         [ #  # ]:          0 :         pl_err(log, "Failed parsing variable data: %.*s", PL_STR_FMT(str));
     645                 :          0 :         return false;
     646                 :            :     }
     647                 :            : 
     648                 :            :     return true;
     649                 :            : }
     650                 :            : 
     651                 :         12 : static bool check_bounds(pl_log log, enum pl_var_type type, const pl_var_data data,
     652                 :            :                          const pl_var_data minimum, const pl_var_data maximum)
     653                 :            : {
     654                 :            : #define CHECK_BOUNDS(v, fmt) do                                                 \
     655                 :            : {                                                                               \
     656                 :            :     if (data.v < minimum.v) {                                                   \
     657                 :            :         pl_err(log, "Initial value "fmt" below declared minimum "fmt"!",        \
     658                 :            :                 data.v, minimum.v);                                             \
     659                 :            :         return false;                                                           \
     660                 :            :     }                                                                           \
     661                 :            :     if (data.v > maximum.v) {                                                   \
     662                 :            :         pl_err(log, "Initial value "fmt" above declared maximum "fmt"!",        \
     663                 :            :                 data.v, maximum.v);                                             \
     664                 :            :         return false;                                                           \
     665                 :            :     }                                                                           \
     666                 :            : } while (0)
     667                 :            : 
     668   [ +  +  +  -  :         12 :     switch (type) {
                      - ]
     669                 :          4 :     case PL_VAR_SINT:
     670   [ -  +  -  + ]:          4 :         CHECK_BOUNDS(i, "%d");
     671                 :            :         break;
     672                 :          4 :     case PL_VAR_UINT:
     673   [ -  +  -  + ]:          4 :         CHECK_BOUNDS(u, "%u");
     674                 :            :         break;
     675                 :          4 :     case PL_VAR_FLOAT:
     676   [ -  +  -  + ]:          4 :         CHECK_BOUNDS(f, "%f");
     677                 :            :         break;
     678                 :            :     case PL_VAR_INVALID:
     679                 :            :     case PL_VAR_TYPE_COUNT:
     680                 :          0 :         pl_unreachable();
     681                 :            :     }
     682                 :            : 
     683                 :            : #undef CHECK_BOUNDS
     684                 :            :     return true;
     685                 :            : }
     686                 :            : 
     687                 :         16 : static bool parse_param(pl_log log, void *alloc, pl_str *body,
     688                 :            :                         struct pl_hook_par *out)
     689                 :            : {
     690                 :         16 :     *out = (struct pl_hook_par) {0};
     691                 :         16 :     pl_str minimum = {0};
     692                 :         16 :     pl_str maximum = {0};
     693                 :            :     bool is_enum = false;
     694                 :            : 
     695                 :            :     while (true) {
     696                 :            :         pl_str rest;
     697                 :         64 :         pl_str line = pl_str_strip(pl_str_getline(*body, &rest));
     698                 :            : 
     699         [ +  + ]:         64 :         if (!pl_str_eatstart0(&line, "//!"))
     700                 :            :             break;
     701                 :            : 
     702                 :         48 :         *body = rest;
     703                 :            : 
     704         [ +  + ]:         48 :         if (pl_str_eatstart0(&line, "PARAM")) {
     705                 :         16 :             out->name = pl_strdup0(alloc, pl_str_strip(line));
     706                 :         56 :             continue;
     707                 :            :         }
     708                 :            : 
     709         [ +  + ]:         32 :         if (pl_str_eatstart0(&line, "DESC")) {
     710                 :          4 :             out->description = pl_strdup0(alloc, pl_str_strip(line));
     711                 :          4 :             continue;
     712                 :            :         }
     713                 :            : 
     714         [ +  + ]:         28 :         if (pl_str_eatstart0(&line, "MINIMUM")) {
     715                 :          4 :             minimum = pl_str_strip(line);
     716                 :          4 :             continue;
     717                 :            :         }
     718                 :            : 
     719         [ +  + ]:         24 :         if (pl_str_eatstart0(&line, "MAXIMUM")) {
     720                 :          8 :             maximum = pl_str_strip(line);
     721                 :          8 :             continue;
     722                 :            :         }
     723                 :            : 
     724         [ +  - ]:         16 :         if (pl_str_eatstart0(&line, "TYPE")) {
     725                 :         16 :             line = pl_str_strip(line);
     726                 :         16 :             is_enum = pl_str_eatstart0(&line, "ENUM");
     727                 :         16 :             line = pl_str_strip(line);
     728         [ +  + ]:         16 :             if (pl_str_eatstart0(&line, "DYNAMIC")) {
     729                 :          4 :                 out->mode = PL_HOOK_PAR_DYNAMIC;
     730         [ +  + ]:         12 :             } else if (pl_str_eatstart0(&line, "CONSTANT")) {
     731                 :          4 :                 out->mode = PL_HOOK_PAR_CONSTANT;
     732         [ +  - ]:          8 :             } else if (pl_str_eatstart0(&line, "DEFINE")) {
     733                 :          8 :                 out->mode = PL_HOOK_PAR_DEFINE;
     734                 :          8 :                 out->type = PL_VAR_SINT;
     735         [ -  + ]:          8 :                 if (pl_str_strip(line).len > 0) {
     736         [ #  # ]:          0 :                     pl_err(log, "TYPE DEFINE does not take any extra arguments, "
     737                 :            :                            "unexpected: '%.*s'", PL_STR_FMT(line));
     738                 :          0 :                     return false;
     739                 :            :                 }
     740                 :          8 :                 continue;
     741                 :            :             } else {
     742                 :          0 :                 out->mode = PL_HOOK_PAR_VARIABLE;
     743                 :            :             }
     744                 :            : 
     745                 :          8 :             line = pl_str_strip(line);
     746                 :          8 :             for (const struct pl_named_var *nv = pl_var_glsl_types;
     747         [ +  - ]:         72 :                  nv->glsl_name; nv++)
     748                 :            :             {
     749         [ +  + ]:         72 :                 if (pl_str_equals0(line, nv->glsl_name)) {
     750   [ +  -  -  + ]:          8 :                     if (nv->var.dim_v > 1 || nv->var.dim_m > 1) {
     751                 :          0 :                         pl_err(log, "GLSL type '%s' is incompatible with "
     752                 :            :                                "shader parameters, must be scalar type!",
     753                 :            :                                nv->glsl_name);
     754                 :          0 :                         return false;
     755                 :            :                     }
     756                 :            : 
     757                 :          8 :                     out->type = nv->var.type;
     758   [ -  +  -  - ]:          8 :                     if (is_enum && out->type != PL_VAR_SINT) {
     759                 :          0 :                         pl_err(log, "ENUM is only compatible with type int/DEFINE!");
     760                 :          0 :                         return false;
     761                 :            :                     }
     762                 :          8 :                     goto next;
     763                 :            :                 }
     764                 :            :             }
     765                 :            : 
     766         [ #  # ]:          0 :             pl_err(log, "Unrecognized GLSL type '%.*s'!", PL_STR_FMT(line));
     767                 :          0 :             return false;
     768                 :            :         }
     769                 :            : 
     770         [ #  # ]:          0 :         pl_err(log, "Unrecognized command '%.*s'!", PL_STR_FMT(line));
     771                 :          0 :         return false;
     772                 :            : 
     773                 :            : next: ;
     774                 :            :     }
     775                 :            : 
     776   [ -  +  +  +  :         16 :     switch (out->type) {
                   -  - ]
     777                 :          0 :     case PL_VAR_INVALID:
     778                 :          0 :         pl_err(log, "Missing variable type!");
     779                 :          0 :         return false;
     780                 :          8 :     case PL_VAR_SINT:
     781                 :          8 :         out->minimum.i = INT_MIN;
     782                 :          8 :         out->maximum.i = INT_MAX;
     783                 :          8 :         break;
     784                 :          4 :     case PL_VAR_UINT:
     785                 :          4 :         out->minimum.u = 0;
     786                 :          4 :         out->maximum.u = UINT_MAX;
     787                 :          4 :         break;
     788                 :          4 :     case PL_VAR_FLOAT:
     789                 :          4 :         out->minimum.f = -INFINITY;
     790                 :          4 :         out->maximum.f = INFINITY;
     791                 :          4 :         break;
     792                 :            :     case PL_VAR_TYPE_COUNT:
     793                 :          0 :         pl_unreachable();
     794                 :            :     }
     795                 :            : 
     796                 :         16 :     pl_str initial = pl_str_strip(split_magic(body));
     797         [ -  + ]:         16 :     if (!initial.len) {
     798                 :          0 :         pl_err(log, "Missing initial parameter value!");
     799                 :          0 :         return false;
     800                 :            :     }
     801                 :            : 
     802         [ +  + ]:         16 :     if (is_enum) {
     803                 :            :         PL_ARRAY(const char *) names = {0};
     804         [ -  + ]:          4 :         pl_assert(out->type == PL_VAR_SINT);
     805                 :            :         do {
     806                 :          8 :             pl_str line = pl_str_strip(pl_str_getline(initial, &initial));
     807         [ -  + ]:          8 :             if (!line.len)
     808                 :          0 :                 continue;
     809   [ +  +  -  +  :          8 :             PL_ARRAY_APPEND(alloc, names, pl_strdup0(alloc, line));
                   -  + ]
     810         [ +  + ]:          8 :         } while (initial.len);
     811                 :            : 
     812         [ -  + ]:          4 :         pl_assert(names.num >= 1);
     813                 :          4 :         out->initial.i = 0;
     814                 :          4 :         out->minimum.i = 0;
     815                 :          4 :         out->maximum.i = names.num - 1;
     816                 :          4 :         out->names = names.elem;
     817                 :            :     } else {
     818         [ -  + ]:         12 :         if (!parse_var(log, initial, out->type, &out->initial))
     819                 :            :             return false;
     820         [ -  + ]:         12 :         if (!parse_var(log, minimum, out->type, &out->minimum))
     821                 :            :             return false;
     822         [ -  + ]:         12 :         if (!parse_var(log, maximum, out->type, &out->maximum))
     823                 :            :             return false;
     824         [ -  + ]:         12 :         if (!check_bounds(log, out->type, out->initial, out->minimum, out->maximum))
     825                 :            :             return false;
     826                 :            :     }
     827                 :            : 
     828                 :         16 :     out->data = pl_memdup(alloc, &out->initial, sizeof(out->initial));
     829                 :         16 :     return true;
     830                 :            : }
     831                 :            : 
     832                 :        756 : static enum pl_hook_stage mp_stage_to_pl(pl_str stage)
     833                 :            : {
     834         [ +  - ]:        756 :     if (pl_str_equals0(stage, "RGB"))
     835                 :            :         return PL_HOOK_RGB_INPUT;
     836         [ +  - ]:        756 :     if (pl_str_equals0(stage, "LUMA"))
     837                 :            :         return PL_HOOK_LUMA_INPUT;
     838         [ +  - ]:        756 :     if (pl_str_equals0(stage, "CHROMA"))
     839                 :            :         return PL_HOOK_CHROMA_INPUT;
     840         [ +  - ]:        756 :     if (pl_str_equals0(stage, "ALPHA"))
     841                 :            :         return PL_HOOK_ALPHA_INPUT;
     842         [ +  - ]:        756 :     if (pl_str_equals0(stage, "XYZ"))
     843                 :            :         return PL_HOOK_XYZ_INPUT;
     844                 :            : 
     845         [ +  - ]:        756 :     if (pl_str_equals0(stage, "CHROMA_SCALED"))
     846                 :            :         return PL_HOOK_CHROMA_SCALED;
     847         [ +  - ]:        756 :     if (pl_str_equals0(stage, "ALPHA_SCALED"))
     848                 :            :         return PL_HOOK_ALPHA_SCALED;
     849                 :            : 
     850         [ +  + ]:        756 :     if (pl_str_equals0(stage, "NATIVE"))
     851                 :            :         return PL_HOOK_NATIVE;
     852         [ +  - ]:        744 :     if (pl_str_equals0(stage, "MAINPRESUB"))
     853                 :            :         return PL_HOOK_RGB;
     854         [ +  + ]:        744 :     if (pl_str_equals0(stage, "MAIN"))
     855                 :            :         return PL_HOOK_RGB; // Note: conflicts with above!
     856                 :            : 
     857         [ +  - ]:        726 :     if (pl_str_equals0(stage, "LINEAR"))
     858                 :            :         return PL_HOOK_LINEAR;
     859         [ +  - ]:        726 :     if (pl_str_equals0(stage, "SIGMOID"))
     860                 :            :         return PL_HOOK_SIGMOID;
     861         [ +  - ]:        726 :     if (pl_str_equals0(stage, "PREKERNEL"))
     862                 :            :         return PL_HOOK_PRE_KERNEL;
     863         [ +  - ]:        726 :     if (pl_str_equals0(stage, "POSTKERNEL"))
     864                 :            :         return PL_HOOK_POST_KERNEL;
     865                 :            : 
     866         [ +  - ]:        726 :     if (pl_str_equals0(stage, "SCALED"))
     867                 :            :         return PL_HOOK_SCALED;
     868         [ +  - ]:        726 :     if (pl_str_equals0(stage, "PREOUTPUT"))
     869                 :            :         return PL_HOOK_PRE_OUTPUT;
     870         [ -  + ]:        726 :     if (pl_str_equals0(stage, "OUTPUT"))
     871                 :          0 :         return PL_HOOK_OUTPUT;
     872                 :            : 
     873                 :            :     return 0;
     874                 :            : }
     875                 :            : 
     876                 :         14 : static pl_str pl_stage_to_mp(enum pl_hook_stage stage)
     877                 :            : {
     878   [ -  -  -  -  :         14 :     switch (stage) {
          -  -  -  +  +  
          -  -  -  -  -  
                -  -  - ]
     879                 :            :     case PL_HOOK_RGB_INPUT:     return pl_str0("RGB");
     880                 :            :     case PL_HOOK_LUMA_INPUT:    return pl_str0("LUMA");
     881                 :            :     case PL_HOOK_CHROMA_INPUT:  return pl_str0("CHROMA");
     882                 :            :     case PL_HOOK_ALPHA_INPUT:   return pl_str0("ALPHA");
     883                 :            :     case PL_HOOK_XYZ_INPUT:     return pl_str0("XYZ");
     884                 :            : 
     885                 :            :     case PL_HOOK_CHROMA_SCALED: return pl_str0("CHROMA_SCALED");
     886                 :            :     case PL_HOOK_ALPHA_SCALED:  return pl_str0("ALPHA_SCALED");
     887                 :            : 
     888                 :            :     case PL_HOOK_NATIVE:        return pl_str0("NATIVE");
     889                 :            :     case PL_HOOK_RGB:           return pl_str0("MAINPRESUB");
     890                 :            : 
     891                 :            :     case PL_HOOK_LINEAR:        return pl_str0("LINEAR");
     892                 :            :     case PL_HOOK_SIGMOID:       return pl_str0("SIGMOID");
     893                 :            :     case PL_HOOK_PRE_KERNEL:    return pl_str0("PREKERNEL");
     894                 :            :     case PL_HOOK_POST_KERNEL:   return pl_str0("POSTKERNEL");
     895                 :            : 
     896                 :            :     case PL_HOOK_SCALED:        return pl_str0("SCALED");
     897                 :            :     case PL_HOOK_PRE_OUTPUT:    return pl_str0("PREOUTPUT");
     898                 :            :     case PL_HOOK_OUTPUT:        return pl_str0("OUTPUT");
     899                 :            :     };
     900                 :            : 
     901                 :          0 :     pl_unreachable();
     902                 :            : }
     903                 :            : 
     904                 :            : struct hook_pass {
     905                 :            :     enum pl_hook_stage exec_stages;
     906                 :            :     struct custom_shader_hook hook;
     907                 :            : };
     908                 :            : 
     909                 :            : struct pass_tex {
     910                 :            :     pl_str name;
     911                 :            :     pl_tex tex;
     912                 :            : 
     913                 :            :     // Metadata
     914                 :            :     pl_rect2df rect;
     915                 :            :     struct pl_color_repr repr;
     916                 :            :     struct pl_color_space color;
     917                 :            :     int comps;
     918                 :            : };
     919                 :            : 
     920                 :            : struct hook_priv {
     921                 :            :     pl_log log;
     922                 :            :     pl_gpu gpu;
     923                 :            :     void *alloc;
     924                 :            : 
     925                 :            :     PL_ARRAY(struct hook_pass) hook_passes;
     926                 :            :     PL_ARRAY(struct pl_hook_par) hook_params;
     927                 :            : 
     928                 :            :     // Fixed (for shader-local resources)
     929                 :            :     PL_ARRAY(struct pl_shader_desc) descriptors;
     930                 :            : 
     931                 :            :     // Dynamic per pass
     932                 :            :     enum pl_hook_stage save_stages;
     933                 :            :     PL_ARRAY(struct pass_tex) pass_textures;
     934                 :            :     pl_shader trc_helper;
     935                 :            : 
     936                 :            :     // State for PRNG/frame count
     937                 :            :     int frame_count;
     938                 :            :     uint64_t prng_state[4];
     939                 :            : };
     940                 :            : 
     941                 :         10 : static void hook_reset(void *priv)
     942                 :            : {
     943                 :            :     struct hook_priv *p = priv;
     944                 :         10 :     p->pass_textures.num = 0;
     945                 :         10 : }
     946                 :            : 
     947                 :            : // Context during execution of a hook
     948                 :            : struct hook_ctx {
     949                 :            :     struct hook_priv *priv;
     950                 :            :     const struct pl_hook_params *params;
     951                 :            :     struct pass_tex hooked;
     952                 :            : };
     953                 :            : 
     954                 :         44 : static bool lookup_tex(struct hook_ctx *ctx, pl_str var, float size[2])
     955                 :            : {
     956                 :         44 :     struct hook_priv *p = ctx->priv;
     957                 :         44 :     const struct pl_hook_params *params = ctx->params;
     958                 :            : 
     959         [ +  + ]:         44 :     if (pl_str_equals0(var, "HOOKED")) {
     960         [ -  + ]:         36 :         pl_assert(ctx->hooked.tex);
     961                 :         36 :         size[0] = ctx->hooked.tex->params.w;
     962                 :         36 :         size[1] = ctx->hooked.tex->params.h;
     963                 :         36 :         return true;
     964                 :            :     }
     965                 :            : 
     966         [ -  + ]:          8 :     if (pl_str_equals0(var, "NATIVE_CROPPED")) {
     967                 :          0 :         size[0] = fabs(pl_rect_w(params->src_rect));
     968                 :          0 :         size[1] = fabs(pl_rect_h(params->src_rect));
     969                 :          0 :         return true;
     970                 :            :     }
     971                 :            : 
     972         [ -  + ]:          8 :     if (pl_str_equals0(var, "OUTPUT")) {
     973                 :          0 :         size[0] = abs(pl_rect_w(params->dst_rect));
     974                 :          0 :         size[1] = abs(pl_rect_h(params->dst_rect));
     975                 :          0 :         return true;
     976                 :            :     }
     977                 :            : 
     978         [ -  + ]:          8 :     if (pl_str_equals0(var, "MAIN"))
     979                 :            :         var = pl_str0("MAINPRESUB");
     980                 :            : 
     981         [ +  - ]:          8 :     for (int i = 0; i < p->pass_textures.num; i++) {
     982         [ +  - ]:          8 :         if (pl_str_equals(var, p->pass_textures.elem[i].name)) {
     983                 :          8 :             pl_tex tex = p->pass_textures.elem[i].tex;
     984                 :          8 :             size[0] = tex->params.w;
     985                 :          8 :             size[1] = tex->params.h;
     986                 :          8 :             return true;
     987                 :            :         }
     988                 :            :     }
     989                 :            : 
     990                 :            :     return false;
     991                 :            : }
     992                 :            : 
     993                 :         12 : static bool lookup_var(struct hook_ctx *ctx, pl_str var, float *val)
     994                 :            : {
     995                 :         12 :     struct hook_priv *p = ctx->priv;
     996         [ +  - ]:         40 :     for (int i = 0; i < p->hook_params.num; i++) {
     997                 :         40 :         const struct pl_hook_par *hp = &p->hook_params.elem[i];
     998         [ +  + ]:         40 :         if (pl_str_equals0(var, hp->name)) {
     999   [ +  +  -  - ]:          8 :             switch (hp->type) {
    1000                 :          4 :             case PL_VAR_SINT:  *val = hp->data->i; return true;
    1001                 :          4 :             case PL_VAR_UINT:  *val = hp->data->u; return true;
    1002                 :          0 :             case PL_VAR_FLOAT: *val = hp->data->f; return true;
    1003                 :            :             case PL_VAR_INVALID:
    1004                 :            :             case PL_VAR_TYPE_COUNT:
    1005                 :            :                 break;
    1006                 :            :             }
    1007                 :            : 
    1008                 :          0 :             pl_unreachable();
    1009                 :            :         }
    1010                 :            : 
    1011         [ +  + ]:         32 :         if (hp->names) {
    1012         [ +  - ]:          4 :             for (int j = hp->minimum.i; j <= hp->maximum.i; j++) {
    1013         [ +  - ]:          4 :                 if (pl_str_equals0(var, hp->names[j])) {
    1014                 :          4 :                     *val = j;
    1015                 :          4 :                     return true;
    1016                 :            :                 }
    1017                 :            :             }
    1018                 :            :         }
    1019                 :            :     }
    1020                 :            : 
    1021         [ #  # ]:          0 :     PL_WARN(p, "Variable '%.*s' not found in RPN expression!", PL_STR_FMT(var));
    1022                 :          0 :     return false;
    1023                 :            : }
    1024                 :            : 
    1025                 :            : // Returns whether successful. 'result' is left untouched on failure
    1026                 :         58 : static bool eval_shexpr(struct hook_ctx *ctx,
    1027                 :            :                         const struct shexp expr[MAX_SHEXP_SIZE],
    1028                 :            :                         float *result)
    1029                 :            : {
    1030                 :         58 :     struct hook_priv *p = ctx->priv;
    1031                 :         58 :     float stack[MAX_SHEXP_SIZE] = {0};
    1032                 :            :     int idx = 0; // points to next element to push
    1033                 :            : 
    1034         [ +  - ]:        164 :     for (int i = 0; i < MAX_SHEXP_SIZE; i++) {
    1035   [ -  +  +  -  :        164 :         switch (expr[i].tag) {
                +  +  + ]
    1036                 :         58 :         case SHEXP_END:
    1037                 :         58 :             goto done;
    1038                 :            : 
    1039                 :         26 :         case SHEXP_CONST:
    1040                 :            :             // Since our SHEXPs are bound by MAX_SHEXP_SIZE, it should be
    1041                 :            :             // impossible to overflow the stack
    1042         [ -  + ]:         26 :             assert(idx < MAX_SHEXP_SIZE);
    1043                 :         26 :             stack[idx++] = expr[i].val.cval;
    1044                 :         26 :             continue;
    1045                 :            : 
    1046                 :          0 :         case SHEXP_OP1:
    1047         [ #  # ]:          0 :             if (idx < 1) {
    1048                 :          0 :                 PL_WARN(p, "Stack underflow in RPN expression!");
    1049                 :          0 :                 return false;
    1050                 :            :             }
    1051                 :            : 
    1052         [ #  # ]:          0 :             switch (expr[i].val.op) {
    1053         [ #  # ]:          0 :             case SHEXP_OP_NOT: stack[idx-1] = !stack[idx-1]; break;
    1054                 :          0 :             default: pl_unreachable();
    1055                 :            :             }
    1056                 :          0 :             continue;
    1057                 :            : 
    1058                 :         24 :         case SHEXP_OP2:
    1059         [ -  + ]:         24 :             if (idx < 2) {
    1060                 :          0 :                 PL_WARN(p, "Stack underflow in RPN expression!");
    1061                 :          0 :                 return false;
    1062                 :            :             }
    1063                 :            : 
    1064                 :            :             // Pop the operands in reverse order
    1065                 :         24 :             float op2 = stack[--idx];
    1066                 :         24 :             float op1 = stack[--idx];
    1067                 :            :             float res = 0.0;
    1068   [ -  -  -  +  :         24 :             switch (expr[i].val.op) {
          -  -  +  +  +  
                      - ]
    1069                 :          0 :             case SHEXP_OP_ADD: res = op1 + op2; break;
    1070                 :          0 :             case SHEXP_OP_SUB: res = op1 - op2; break;
    1071                 :          8 :             case SHEXP_OP_MUL: res = op1 * op2; break;
    1072                 :          0 :             case SHEXP_OP_DIV: res = op1 / op2; break;
    1073                 :          0 :             case SHEXP_OP_MOD: res = fmodf(op1, op2); break;
    1074         [ +  - ]:          4 :             case SHEXP_OP_GT:  res = op1 > op2; break;
    1075         [ -  + ]:          8 :             case SHEXP_OP_LT:  res = op1 < op2; break;
    1076         [ -  + ]:          4 :             case SHEXP_OP_EQ:  res = fabsf(op1 - op2) <= 1e-6 * fmaxf(op1, op2); break;
    1077                 :          0 :             case SHEXP_OP_NOT: pl_unreachable();
    1078                 :            :             }
    1079                 :            : 
    1080         [ -  + ]:         24 :             if (!isfinite(res)) {
    1081                 :          0 :                 PL_WARN(p, "Illegal operation in RPN expression!");
    1082                 :          0 :                 return false;
    1083                 :            :             }
    1084                 :            : 
    1085                 :         24 :             stack[idx++] = res;
    1086                 :         24 :             continue;
    1087                 :            : 
    1088                 :         44 :         case SHEXP_TEX_W:
    1089                 :         44 :         case SHEXP_TEX_H: {
    1090                 :         44 :             pl_str name = expr[i].val.varname;
    1091                 :            :             float size[2];
    1092                 :            : 
    1093         [ -  + ]:         44 :             if (!lookup_tex(ctx, name, size)) {
    1094         [ #  # ]:          0 :                 PL_WARN(p, "Variable '%.*s' not found in RPN expression!",
    1095                 :            :                         PL_STR_FMT(name));
    1096                 :          0 :                 return false;
    1097                 :            :             }
    1098                 :            : 
    1099         [ +  + ]:         44 :             stack[idx++] = (expr[i].tag == SHEXP_TEX_W) ? size[0] : size[1];
    1100                 :         44 :             continue;
    1101                 :            :         }
    1102                 :            : 
    1103                 :         24 :         case SHEXP_VAR: {
    1104                 :         12 :             pl_str name = expr[i].val.varname;
    1105                 :            :             float val;
    1106         [ -  + ]:         12 :             if (!lookup_var(ctx, name, &val))
    1107                 :          0 :                 return false;
    1108                 :         12 :             stack[idx++] = val;
    1109                 :         12 :             continue;
    1110                 :            :         }
    1111                 :            :         }
    1112                 :            :     }
    1113                 :            : 
    1114                 :          0 : done:
    1115                 :            :     // Return the single stack element
    1116         [ -  + ]:         58 :     if (idx != 1) {
    1117                 :          0 :         PL_WARN(p, "Malformed stack after RPN expression!");
    1118                 :          0 :         return false;
    1119                 :            :     }
    1120                 :            : 
    1121                 :         58 :     *result = stack[0];
    1122                 :         58 :     return true;
    1123                 :            : }
    1124                 :            : 
    1125                 :            : static double prng_step(uint64_t s[4])
    1126                 :            : {
    1127                 :         18 :     const uint64_t result = s[0] + s[3];
    1128                 :         18 :     const uint64_t t = s[1] << 17;
    1129                 :            : 
    1130                 :         18 :     s[2] ^= s[0];
    1131                 :         18 :     s[3] ^= s[1];
    1132                 :         18 :     s[1] ^= s[2];
    1133                 :         18 :     s[0] ^= s[3];
    1134                 :            : 
    1135                 :         18 :     s[2] ^= t;
    1136                 :         18 :     s[3] = (s[3] << 45) | (s[3] >> (64 - 45));
    1137                 :         18 :     return (result >> 11) * 0x1.0p-53;
    1138                 :            : }
    1139                 :            : 
    1140                 :         12 : static bool bind_pass_tex(pl_shader sh, pl_str name,
    1141                 :            :                           const struct pass_tex *ptex,
    1142                 :            :                           const pl_rect2df *rect,
    1143                 :            :                           bool hooked, bool mainpresub)
    1144                 :            : {
    1145                 :            :     ident_t id, pos, pt;
    1146                 :            : 
    1147                 :            :     // Compatibility with mpv texture binding semantics
    1148                 :         12 :     id = sh_bind(sh, ptex->tex, PL_TEX_ADDRESS_CLAMP, PL_TEX_SAMPLE_LINEAR,
    1149                 :            :                  "hook_tex", rect, &pos, &pt);
    1150         [ +  - ]:         12 :     if (!id)
    1151                 :            :         return false;
    1152                 :            : 
    1153         [ +  - ]:         24 :     GLSLH("#define %.*s_raw "$" \n", PL_STR_FMT(name), id);
    1154                 :         12 :     GLSLH("#define %.*s_pos "$" \n", PL_STR_FMT(name), pos);
    1155                 :         12 :     GLSLH("#define %.*s_map "$"_map \n", PL_STR_FMT(name), pos);
    1156                 :         12 :     GLSLH("#define %.*s_size vec2(textureSize("$", 0)) \n", PL_STR_FMT(name), id);
    1157                 :         12 :     GLSLH("#define %.*s_pt "$" \n", PL_STR_FMT(name), pt);
    1158                 :            : 
    1159                 :         12 :     float off[2] = { ptex->rect.x0, ptex->rect.y0 };
    1160                 :         12 :     GLSLH("#define %.*s_off "$" \n", PL_STR_FMT(name),
    1161                 :            :           sh_var(sh, (struct pl_shader_var) {
    1162                 :            :               .var = pl_var_vec2("offset"),
    1163                 :            :               .data = off,
    1164                 :            :     }));
    1165                 :            : 
    1166                 :         12 :     struct pl_color_repr repr = ptex->repr;
    1167                 :         12 :     ident_t scale = SH_FLOAT(pl_color_repr_normalize(&repr));
    1168                 :         12 :     GLSLH("#define %.*s_mul "$" \n", PL_STR_FMT(name), scale);
    1169                 :            : 
    1170                 :            :     // Compatibility with mpv
    1171                 :         12 :     GLSLH("#define %.*s_rot mat2(1.0, 0.0, 0.0, 1.0) \n", PL_STR_FMT(name));
    1172                 :            : 
    1173                 :            :     // Sampling function boilerplate
    1174                 :         12 :     GLSLH("#define %.*s_tex(pos) ("$" * vec4(textureLod("$", pos, 0.0))) \n",
    1175                 :            :           PL_STR_FMT(name), scale, id);
    1176                 :         12 :     GLSLH("#define %.*s_texOff(off) (%.*s_tex("$" + "$" * vec2(off))) \n",
    1177                 :            :           PL_STR_FMT(name), PL_STR_FMT(name), pos, pt);
    1178                 :            : 
    1179                 :         12 :     bool can_gather = ptex->tex->params.format->gatherable;
    1180         [ +  + ]:         12 :     if (can_gather) {
    1181                 :          6 :         GLSLH("#define %.*s_gather(pos, c) ("$" * vec4(textureGather("$", pos, c))) \n",
    1182                 :            :               PL_STR_FMT(name), scale, id);
    1183                 :            :     }
    1184                 :            : 
    1185         [ +  + ]:         12 :     if (hooked) {
    1186                 :          8 :         GLSLH("#define HOOKED_raw %.*s_raw \n", PL_STR_FMT(name));
    1187                 :          8 :         GLSLH("#define HOOKED_pos %.*s_pos \n", PL_STR_FMT(name));
    1188                 :          8 :         GLSLH("#define HOOKED_size %.*s_size \n", PL_STR_FMT(name));
    1189                 :          8 :         GLSLH("#define HOOKED_rot %.*s_rot \n", PL_STR_FMT(name));
    1190                 :          8 :         GLSLH("#define HOOKED_off %.*s_off \n", PL_STR_FMT(name));
    1191                 :          8 :         GLSLH("#define HOOKED_pt %.*s_pt \n", PL_STR_FMT(name));
    1192                 :          8 :         GLSLH("#define HOOKED_map %.*s_map \n", PL_STR_FMT(name));
    1193                 :          8 :         GLSLH("#define HOOKED_mul %.*s_mul \n", PL_STR_FMT(name));
    1194                 :          8 :         GLSLH("#define HOOKED_tex %.*s_tex \n", PL_STR_FMT(name));
    1195                 :          8 :         GLSLH("#define HOOKED_texOff %.*s_texOff \n", PL_STR_FMT(name));
    1196         [ +  + ]:          8 :         if (can_gather)
    1197                 :          4 :             GLSLH("#define HOOKED_gather %.*s_gather \n", PL_STR_FMT(name));
    1198                 :            :     }
    1199                 :            : 
    1200         [ +  + ]:         12 :     if (mainpresub) {
    1201                 :          4 :         GLSLH("#define MAIN_raw MAINPRESUB_raw \n");
    1202                 :          4 :         GLSLH("#define MAIN_pos MAINPRESUB_pos \n");
    1203                 :          4 :         GLSLH("#define MAIN_size MAINPRESUB_size \n");
    1204                 :          4 :         GLSLH("#define MAIN_rot MAINPRESUB_rot \n");
    1205                 :          4 :         GLSLH("#define MAIN_off MAINPRESUB_off \n");
    1206                 :          4 :         GLSLH("#define MAIN_pt MAINPRESUB_pt \n");
    1207                 :          4 :         GLSLH("#define MAIN_map MAINPRESUB_map \n");
    1208                 :          4 :         GLSLH("#define MAIN_mul MAINPRESUB_mul \n");
    1209                 :          4 :         GLSLH("#define MAIN_tex MAINPRESUB_tex \n");
    1210                 :          4 :         GLSLH("#define MAIN_texOff MAINPRESUB_texOff \n");
    1211         [ +  + ]:          4 :         if (can_gather)
    1212                 :          2 :             GLSLH("#define MAIN_gather MAINPRESUB_gather \n");
    1213                 :            :     }
    1214                 :            : 
    1215                 :            :     return true;
    1216                 :            : }
    1217                 :            : 
    1218                 :         26 : static void save_pass_tex(struct hook_priv *p, struct pass_tex ptex)
    1219                 :            : {
    1220                 :            : 
    1221         [ +  + ]:         38 :     for (int i = 0; i < p->pass_textures.num; i++) {
    1222         [ +  + ]:         20 :         if (!pl_str_equals(p->pass_textures.elem[i].name, ptex.name))
    1223                 :            :             continue;
    1224                 :            : 
    1225                 :          8 :         p->pass_textures.elem[i] = ptex;
    1226                 :          8 :         return;
    1227                 :            :     }
    1228                 :            : 
    1229                 :            :     // No texture with this name yet, append new one
    1230   [ +  +  -  +  :         18 :     PL_ARRAY_APPEND(p->alloc, p->pass_textures, ptex);
                   -  + ]
    1231                 :            : }
    1232                 :            : 
    1233                 :         14 : static struct pl_hook_res hook_hook(void *priv, const struct pl_hook_params *params)
    1234                 :            : {
    1235                 :            :     struct hook_priv *p = priv;
    1236                 :         14 :     pl_str stage = pl_stage_to_mp(params->stage);
    1237                 :         14 :     struct pl_hook_res res = {0};
    1238                 :            : 
    1239                 :         14 :     pl_shader sh = NULL;
    1240                 :         14 :     struct hook_ctx ctx = {
    1241                 :            :         .priv = p,
    1242                 :            :         .params = params,
    1243                 :            :         .hooked = {
    1244                 :            :             .name  = stage,
    1245                 :         14 :             .tex   = params->tex,
    1246                 :            :             .rect  = params->rect,
    1247                 :            :             .repr  = params->repr,
    1248                 :            :             .color = params->color,
    1249                 :         14 :             .comps = params->components,
    1250                 :            :         },
    1251                 :            :     };
    1252                 :            : 
    1253                 :            :     // Save the input texture if needed
    1254         [ +  + ]:         14 :     if (p->save_stages & params->stage) {
    1255         [ +  - ]:         16 :         PL_TRACE(p, "Saving input texture '%.*s' for binding",
    1256                 :            :                  PL_STR_FMT(ctx.hooked.name));
    1257                 :          8 :         save_pass_tex(p, ctx.hooked);
    1258                 :            :     }
    1259                 :            : 
    1260         [ +  + ]:         44 :     for (int n = 0; n < p->hook_passes.num; n++) {
    1261                 :         30 :         const struct hook_pass *pass = &p->hook_passes.elem[n];
    1262         [ +  + ]:         30 :         if (!(pass->exec_stages & params->stage))
    1263                 :         12 :             continue;
    1264                 :            : 
    1265                 :         22 :         const struct custom_shader_hook *hook = &pass->hook;
    1266   [ +  -  +  - ]:         66 :         PL_TRACE(p, "Executing hook pass %d on stage '%.*s': %.*s",
    1267                 :            :                  n, PL_STR_FMT(stage), PL_STR_FMT(hook->pass_desc));
    1268                 :            : 
    1269                 :            :         // Test for execution condition
    1270                 :         22 :         float run = 0;
    1271         [ -  + ]:         22 :         if (!eval_shexpr(&ctx, hook->cond, &run))
    1272                 :          0 :             goto error;
    1273                 :            : 
    1274         [ +  + ]:         22 :         if (!run) {
    1275                 :          4 :             PL_TRACE(p, "Skipping hook due to condition");
    1276                 :          4 :             continue;
    1277                 :            :         }
    1278                 :            : 
    1279                 :            :         // Generate a new shader object
    1280                 :         18 :         sh = pl_dispatch_begin(params->dispatch);
    1281                 :            : 
    1282                 :            :         // Bind all necessary input textures
    1283         [ +  - ]:         40 :         for (int i = 0; i < PL_ARRAY_SIZE(hook->bind_tex); i++) {
    1284                 :         40 :             pl_str texname = hook->bind_tex[i];
    1285         [ +  + ]:         40 :             if (!texname.len)
    1286                 :            :                 break;
    1287                 :            : 
    1288                 :            :             // Convenience alias, to allow writing shaders that are oblivious
    1289                 :            :             // of the exact stage they hooked. This simply translates to
    1290                 :            :             // whatever stage actually fired the hook.
    1291                 :            :             bool hooked = false, mainpresub = false;
    1292         [ +  + ]:         22 :             if (pl_str_equals0(texname, "HOOKED")) {
    1293                 :            :                 // Continue with binding this, under the new name
    1294                 :            :                 texname = stage;
    1295                 :            :                 hooked = true;
    1296                 :            :             }
    1297                 :            : 
    1298                 :            :             // Compatibility alias, because MAIN and MAINPRESUB mean the same
    1299                 :            :             // thing to libplacebo, but user shaders are still written as
    1300                 :            :             // though they can be different concepts.
    1301         [ +  - ]:         22 :             if (pl_str_equals0(texname, "MAIN") ||
    1302         [ +  + ]:         22 :                 pl_str_equals0(texname, "MAINPRESUB"))
    1303                 :            :             {
    1304                 :            :                 texname = pl_str0("MAINPRESUB");
    1305                 :            :                 mainpresub = true;
    1306                 :            :             }
    1307                 :            : 
    1308         [ +  + ]:         32 :             for (int j = 0; j < p->descriptors.num; j++) {
    1309         [ +  + ]:         20 :                 if (pl_str_equals0(texname, p->descriptors.elem[j].desc.name)) {
    1310                 :            :                     // Directly bind this, no need to bother with all the
    1311                 :            :                     // `bind_pass_tex` boilerplate
    1312                 :         10 :                     ident_t id = sh_desc(sh, p->descriptors.elem[j]);
    1313         [ +  - ]:         20 :                     GLSLH("#define %.*s "$" \n", PL_STR_FMT(texname), id);
    1314                 :            : 
    1315         [ +  + ]:         10 :                     if (p->descriptors.elem[j].desc.type == PL_DESC_SAMPLED_TEX) {
    1316                 :          4 :                         GLSLH("#define %.*s_tex(pos) (textureLod("$", pos, 0.0)) \n",
    1317                 :            :                               PL_STR_FMT(texname), id);
    1318                 :            :                     }
    1319                 :         10 :                     goto next_bind;
    1320                 :            :                 }
    1321                 :            :             }
    1322                 :            : 
    1323         [ +  - ]:         16 :             for (int j = 0; j < p->pass_textures.num; j++) {
    1324         [ +  + ]:         16 :                 if (pl_str_equals(texname, p->pass_textures.elem[j].name)) {
    1325                 :            :                     // Note: We bind the whole texture, rather than
    1326                 :            :                     // hooked.rect, because user shaders in general are not
    1327                 :            :                     // designed to handle cropped input textures.
    1328                 :            :                     const struct pass_tex *ptex = &p->pass_textures.elem[j];
    1329                 :         12 :                     pl_rect2df rect = {
    1330                 :         12 :                         0, 0, ptex->tex->params.w, ptex->tex->params.h,
    1331                 :            :                     };
    1332                 :            : 
    1333   [ -  +  -  - ]:         12 :                     if (hook->offset_align && pl_str_equals(texname, stage)) {
    1334                 :          0 :                         float sx = pl_rect_w(ctx.hooked.rect) / pl_rect_w(params->src_rect),
    1335                 :          0 :                               sy = pl_rect_h(ctx.hooked.rect) / pl_rect_h(params->src_rect),
    1336                 :          0 :                               ox = ctx.hooked.rect.x0 - sx * params->src_rect.x0,
    1337                 :          0 :                               oy = ctx.hooked.rect.y0 - sy * params->src_rect.y0;
    1338                 :            : 
    1339                 :          0 :                         PL_TRACE(p, "Aligning plane with ref: %f %f", ox, oy);
    1340                 :          0 :                         pl_rect2df_offset(&rect, ox, oy);
    1341                 :            :                     }
    1342                 :            : 
    1343         [ -  + ]:         12 :                     if (!bind_pass_tex(sh, texname, &p->pass_textures.elem[j],
    1344                 :            :                                        &rect, hooked, mainpresub))
    1345                 :            :                     {
    1346                 :          0 :                         goto error;
    1347                 :            :                     }
    1348                 :         12 :                     goto next_bind;
    1349                 :            :                 }
    1350                 :            :             }
    1351                 :            : 
    1352                 :            :             // If none of the above matched, this is an unknown texture name,
    1353                 :            :             // so silently ignore this pass to match the mpv behavior
    1354         [ #  # ]:          0 :             PL_TRACE(p, "Skipping hook due to no texture named '%.*s'.",
    1355                 :            :                      PL_STR_FMT(texname));
    1356                 :          0 :             pl_dispatch_abort(params->dispatch, &sh);
    1357                 :          0 :             goto next_pass;
    1358                 :            : 
    1359                 :         22 :     next_bind: ; // outer 'continue'
    1360                 :            :         }
    1361                 :            : 
    1362                 :            :         // Set up the input variables
    1363                 :         18 :         p->frame_count++;
    1364                 :         18 :         GLSLH("#define frame "$" \n", sh_var(sh, (struct pl_shader_var) {
    1365                 :            :             .var = pl_var_int("frame"),
    1366                 :            :             .data = &p->frame_count,
    1367                 :            :             .dynamic = true,
    1368                 :            :         }));
    1369                 :            : 
    1370                 :         18 :         float random = prng_step(p->prng_state);
    1371                 :         18 :         GLSLH("#define random "$" \n", sh_var(sh, (struct pl_shader_var) {
    1372                 :            :             .var = pl_var_float("random"),
    1373                 :            :             .data = &random,
    1374                 :            :             .dynamic = true,
    1375                 :            :         }));
    1376                 :            : 
    1377                 :         18 :         float src_size[2] = { pl_rect_w(params->src_rect), pl_rect_h(params->src_rect) };
    1378                 :         18 :         GLSLH("#define input_size "$" \n", sh_var(sh, (struct pl_shader_var) {
    1379                 :            :             .var = pl_var_vec2("input_size"),
    1380                 :            :             .data = src_size,
    1381                 :            :         }));
    1382                 :            : 
    1383                 :         18 :         float dst_size[2] = { pl_rect_w(params->dst_rect), pl_rect_h(params->dst_rect) };
    1384                 :         18 :         GLSLH("#define target_size "$" \n", sh_var(sh, (struct pl_shader_var) {
    1385                 :            :             .var = pl_var_vec2("target_size"),
    1386                 :            :             .data = dst_size,
    1387                 :            :         }));
    1388                 :            : 
    1389                 :         18 :         float tex_off[2] = { params->src_rect.x0, params->src_rect.y0 };
    1390                 :         18 :         GLSLH("#define tex_offset "$" \n", sh_var(sh, (struct pl_shader_var) {
    1391                 :            :             .var = pl_var_vec2("tex_offset"),
    1392                 :            :             .data = tex_off,
    1393                 :            :         }));
    1394                 :            : 
    1395                 :            :         // Custom parameters
    1396         [ +  + ]:         50 :         for (int i = 0; i < p->hook_params.num; i++) {
    1397                 :         32 :             const struct pl_hook_par *hp = &p->hook_params.elem[i];
    1398   [ +  +  +  -  :         32 :             switch (hp->mode) {
                      - ]
    1399                 :            :             case PL_HOOK_PAR_VARIABLE:
    1400                 :            :             case PL_HOOK_PAR_DYNAMIC:
    1401                 :          8 :                 GLSLH("#define %s "$" \n", hp->name,
    1402                 :            :                       sh_var(sh, (struct pl_shader_var) {
    1403                 :            :                         .var = {
    1404                 :            :                             .name = hp->name,
    1405                 :            :                             .type = hp->type,
    1406                 :            :                             .dim_v = 1,
    1407                 :            :                             .dim_m = 1,
    1408                 :            :                             .dim_a = 1,
    1409                 :            :                         },
    1410                 :            :                         .data = hp->data,
    1411                 :            :                         .dynamic = hp->mode == PL_HOOK_PAR_DYNAMIC,
    1412                 :            :                 }));
    1413                 :          8 :                 break;
    1414                 :            : 
    1415                 :            :             case PL_HOOK_PAR_CONSTANT:
    1416                 :          8 :                 GLSLH("#define %s "$" \n", hp->name,
    1417                 :            :                       sh_const(sh, (struct pl_shader_const) {
    1418                 :            :                         .name = hp->name,
    1419                 :            :                         .type = hp->type,
    1420                 :            :                         .data = hp->data,
    1421                 :            :                         .compile_time = true,
    1422                 :            :                 }));
    1423                 :          8 :                 break;
    1424                 :            : 
    1425                 :            :             case PL_HOOK_PAR_DEFINE:
    1426                 :         16 :                 GLSLH("#define %s %d \n", hp->name, hp->data->i);
    1427                 :         16 :                 break;
    1428                 :            : 
    1429                 :            :             case PL_HOOK_PAR_MODE_COUNT:
    1430                 :          0 :                 pl_unreachable();
    1431                 :            :             }
    1432                 :            : 
    1433         [ +  + ]:         32 :             if (hp->names) {
    1434         [ +  + ]:         24 :                 for (int j = hp->minimum.i; j <= hp->maximum.i; j++)
    1435                 :         16 :                     GLSLH("#define %s %d \n", hp->names[j], j);
    1436                 :            :             }
    1437                 :            :         }
    1438                 :            : 
    1439                 :            :         // Helper sub-shaders
    1440                 :         18 :         uint64_t sh_id = SH_PARAMS(sh).id;
    1441                 :         18 :         pl_shader_reset(p->trc_helper, pl_shader_params(
    1442                 :            :             .id = ++sh_id,
    1443                 :            :             .gpu = p->gpu,
    1444                 :            :         ));
    1445                 :         18 :         pl_shader_linearize(p->trc_helper, params->orig_color);
    1446                 :         18 :         GLSLH("#define linearize "$" \n", sh_subpass(sh, p->trc_helper));
    1447                 :            : 
    1448                 :         18 :         pl_shader_reset(p->trc_helper, pl_shader_params(
    1449                 :            :             .id = ++sh_id,
    1450                 :            :             .gpu = p->gpu,
    1451                 :            :         ));
    1452                 :         18 :         pl_shader_delinearize(p->trc_helper, params->orig_color);
    1453                 :         18 :         GLSLH("#define delinearize "$" \n", sh_subpass(sh, p->trc_helper));
    1454                 :            : 
    1455                 :            :         // Load and run the user shader itself
    1456                 :         18 :         sh_append_str(sh, SH_BUF_HEADER, hook->pass_body);
    1457         [ +  - ]:         36 :         sh_describef(sh, "%.*s", PL_STR_FMT(hook->pass_desc));
    1458                 :            : 
    1459                 :            :         // Resolve output size and create framebuffer
    1460                 :         18 :         float out_size[2] = {0};
    1461   [ +  -  -  + ]:         36 :         if (!eval_shexpr(&ctx, hook->width,  &out_size[0]) ||
    1462                 :         18 :             !eval_shexpr(&ctx, hook->height, &out_size[1]))
    1463                 :            :         {
    1464                 :          0 :             goto error;
    1465                 :            :         }
    1466                 :            : 
    1467                 :         18 :         int out_w = roundf(out_size[0]),
    1468                 :         18 :             out_h = roundf(out_size[1]);
    1469                 :            : 
    1470         [ -  + ]:         18 :         if (!sh_require(sh, PL_SHADER_SIG_NONE, out_w, out_h))
    1471                 :          0 :             goto error;
    1472                 :            : 
    1473                 :            :         // Generate a new texture to store the render result
    1474                 :            :         pl_tex fbo;
    1475                 :         18 :         fbo = params->get_tex(params->priv, out_w, out_h);
    1476         [ -  + ]:         18 :         if (!fbo) {
    1477                 :          0 :             PL_ERR(p, "Failed dispatching hook: `get_tex` callback failed?");
    1478                 :          0 :             goto error;
    1479                 :            :         }
    1480                 :            : 
    1481                 :            :         bool ok;
    1482         [ -  + ]:         18 :         if (hook->is_compute) {
    1483                 :            : 
    1484         [ #  # ]:          0 :             if (!sh_try_compute(sh, hook->threads_w, hook->threads_h, false, 0) ||
    1485         [ #  # ]:          0 :                 !fbo->params.storable)
    1486                 :            :             {
    1487                 :          0 :                 PL_ERR(p, "Failed dispatching COMPUTE shader");
    1488                 :          0 :                 goto error;
    1489                 :            :             }
    1490                 :            : 
    1491                 :          0 :             GLSLP("#define out_image "$" \n", sh_desc(sh, (struct pl_shader_desc) {
    1492                 :            :                 .binding.object = fbo,
    1493                 :            :                 .desc = {
    1494                 :            :                     .name = "out_image",
    1495                 :            :                     .type = PL_DESC_STORAGE_IMG,
    1496                 :            :                     .access = PL_DESC_ACCESS_WRITEONLY,
    1497                 :            :                 },
    1498                 :            :             }));
    1499                 :            : 
    1500                 :          0 :             sh->output = PL_SHADER_SIG_NONE;
    1501                 :            : 
    1502                 :          0 :             GLSL("hook(); \n");
    1503                 :          0 :             ok = pl_dispatch_compute(params->dispatch, pl_dispatch_compute_params(
    1504                 :            :                 .shader = &sh,
    1505                 :            :                 .dispatch_size = {
    1506                 :            :                     // Round up as many blocks as are needed to cover the image
    1507                 :            :                     PL_DIV_UP(out_w, hook->block_w),
    1508                 :            :                     PL_DIV_UP(out_h, hook->block_h),
    1509                 :            :                     1,
    1510                 :            :                 },
    1511                 :            :                 .width  = out_w,
    1512                 :            :                 .height = out_h,
    1513                 :            :             ));
    1514                 :            : 
    1515                 :            :         } else {
    1516                 :            : 
    1517                 :            :             // Default non-COMPUTE shaders to explicitly use fragment shaders
    1518                 :            :             // only, to avoid breaking things like fwidth()
    1519         [ -  + ]:         18 :             sh->type = PL_DEF(sh->type, SH_FRAGMENT);
    1520                 :            : 
    1521                 :         18 :             GLSL("vec4 color = hook(); \n");
    1522                 :         18 :             ok = pl_dispatch_finish(params->dispatch, pl_dispatch_params(
    1523                 :            :                 .shader = &sh,
    1524                 :            :                 .target = fbo,
    1525                 :            :             ));
    1526                 :            : 
    1527                 :            :         }
    1528                 :            : 
    1529         [ -  + ]:         18 :         if (!ok)
    1530                 :          0 :             goto error;
    1531                 :            : 
    1532                 :         18 :         float sx = (float) out_w / ctx.hooked.tex->params.w,
    1533                 :         18 :               sy = (float) out_h / ctx.hooked.tex->params.h,
    1534                 :         18 :               x0 = sx * ctx.hooked.rect.x0 + hook->offset[0],
    1535                 :         18 :               y0 = sy * ctx.hooked.rect.y0 + hook->offset[1];
    1536                 :            : 
    1537                 :         18 :         pl_rect2df new_rect = {
    1538                 :            :             x0,
    1539                 :            :             y0,
    1540                 :         18 :             x0 + sx * pl_rect_w(ctx.hooked.rect),
    1541                 :         18 :             y0 + sy * pl_rect_h(ctx.hooked.rect),
    1542                 :            :         };
    1543                 :            : 
    1544         [ -  + ]:         18 :         if (hook->offset_align) {
    1545                 :          0 :             float rx = pl_rect_w(new_rect) / pl_rect_w(params->src_rect),
    1546                 :          0 :                   ry = pl_rect_h(new_rect) / pl_rect_h(params->src_rect),
    1547                 :          0 :                   ox = rx * params->src_rect.x0 - sx * ctx.hooked.rect.x0,
    1548                 :          0 :                   oy = ry * params->src_rect.y0 - sy * ctx.hooked.rect.y0;
    1549                 :            : 
    1550                 :          0 :             pl_rect2df_offset(&new_rect, ox, oy);
    1551                 :            :         }
    1552                 :            : 
    1553                 :            :         // Save the result of this shader invocation
    1554                 :         18 :         struct pass_tex ptex = {
    1555         [ +  + ]:         18 :             .name  = hook->save_tex.len ? hook->save_tex : stage,
    1556                 :            :             .tex   = fbo,
    1557                 :            :             .repr  = ctx.hooked.repr,
    1558                 :            :             .color = ctx.hooked.color,
    1559         [ +  + ]:         18 :             .comps = PL_DEF(hook->comps, ctx.hooked.comps),
    1560                 :            :             .rect  = new_rect,
    1561                 :            :         };
    1562                 :            : 
    1563                 :            :         // It's assumed that users will correctly normalize the input
    1564                 :         18 :         pl_color_repr_normalize(&ptex.repr);
    1565                 :            : 
    1566         [ +  - ]:         36 :         PL_TRACE(p, "Saving output texture '%.*s' from hook execution on '%.*s'",
    1567                 :            :                  PL_STR_FMT(ptex.name), PL_STR_FMT(stage));
    1568                 :            : 
    1569                 :         18 :         save_pass_tex(p, ptex);
    1570                 :            : 
    1571                 :            :         // Update the result object, unless we saved to a different name
    1572         [ +  + ]:         18 :         if (pl_str_equals(ptex.name, stage)) {
    1573                 :         14 :             ctx.hooked = ptex;
    1574                 :         14 :             res = (struct pl_hook_res) {
    1575                 :            :                 .output     = PL_HOOK_SIG_TEX,
    1576                 :            :                 .tex        = fbo,
    1577                 :            :                 .repr       = ptex.repr,
    1578                 :            :                 .color      = ptex.color,
    1579                 :         14 :                 .components = ptex.comps,
    1580                 :            :                 .rect       = new_rect,
    1581                 :            :             };
    1582                 :            :         }
    1583                 :            : 
    1584                 :         22 : next_pass: ;
    1585                 :            :     }
    1586                 :            : 
    1587                 :         14 :     return res;
    1588                 :            : 
    1589                 :            : error:
    1590                 :          0 :     pl_dispatch_abort(params->dispatch, &sh);
    1591                 :          0 :     return (struct pl_hook_res) { .failed = true };
    1592                 :            : }
    1593                 :            : 
    1594                 :         10 : const struct pl_hook *pl_mpv_user_shader_parse(pl_gpu gpu,
    1595                 :            :                                                const char *shader_text,
    1596                 :            :                                                size_t shader_len)
    1597                 :            : {
    1598         [ -  + ]:         10 :     if (!shader_len)
    1599                 :            :         return NULL;
    1600                 :            : 
    1601                 :         10 :     pl_str shader = { (uint8_t *) shader_text, shader_len };
    1602                 :            : 
    1603                 :         10 :     struct pl_hook *hook = pl_zalloc_obj(NULL, hook, struct hook_priv);
    1604                 :         10 :     struct hook_priv *p = PL_PRIV(hook);
    1605                 :            : 
    1606                 :         10 :     *hook = (struct pl_hook) {
    1607                 :            :         .input = PL_HOOK_SIG_TEX,
    1608                 :            :         .priv = p,
    1609                 :            :         .reset = hook_reset,
    1610                 :            :         .hook = hook_hook,
    1611                 :            :         .signature = pl_str_hash(shader),
    1612                 :            :     };
    1613                 :            : 
    1614                 :         10 :     *p = (struct hook_priv) {
    1615                 :         10 :         .log = gpu->log,
    1616                 :            :         .gpu = gpu,
    1617                 :            :         .alloc = hook,
    1618                 :         10 :         .trc_helper = pl_shader_alloc(gpu->log, NULL),
    1619                 :            :         .prng_state = {
    1620                 :            :             // Determined by fair die roll
    1621                 :            :             0xb76d71f9443c228allu, 0x93a02092fc4807e8llu,
    1622                 :            :             0x06d81748f838bd07llu, 0x9381ee129dddce6cllu,
    1623                 :            :         },
    1624                 :            :     };
    1625                 :            : 
    1626         [ +  - ]:         10 :     shader = pl_strdup(hook, shader);
    1627                 :            : 
    1628                 :            :     // Skip all garbage (e.g. comments) before the first header
    1629                 :         10 :     int pos = pl_str_find(shader, pl_str0("//!"));
    1630         [ -  + ]:         10 :     if (pos < 0) {
    1631                 :          0 :         PL_ERR(gpu, "Shader appears to contain no headers?");
    1632                 :          0 :         goto error;
    1633                 :            :     }
    1634         [ +  - ]:         10 :     shader = pl_str_drop(shader, pos);
    1635                 :            : 
    1636                 :            :     // Loop over the file
    1637         [ +  + ]:         58 :     while (shader.len > 0)
    1638                 :            :     {
    1639                 :            :         // Peek at the first header to dispatch the right type
    1640         [ +  + ]:         48 :         if (pl_str_startswith0(shader, "//!TEXTURE")) {
    1641                 :            :             struct pl_shader_desc sd;
    1642         [ -  + ]:          6 :             if (!parse_tex(gpu, hook, &shader, &sd))
    1643                 :          0 :                 goto error;
    1644                 :            : 
    1645                 :          6 :             PL_INFO(gpu, "Registering named texture '%s'", sd.desc.name);
    1646   [ +  -  -  -  :          6 :             PL_ARRAY_APPEND(hook, p->descriptors, sd);
                   -  - ]
    1647                 :          6 :             continue;
    1648                 :            :         }
    1649                 :            : 
    1650         [ +  + ]:         42 :         if (pl_str_startswith0(shader, "//!BUFFER")) {
    1651                 :            :             struct pl_shader_desc sd;
    1652         [ -  + ]:          4 :             if (!parse_buf(gpu, hook, &shader, &sd))
    1653                 :          0 :                 goto error;
    1654                 :            : 
    1655                 :          4 :             PL_INFO(gpu, "Registering named buffer '%s'", sd.desc.name);
    1656   [ -  +  -  +  :          4 :             PL_ARRAY_APPEND(hook, p->descriptors, sd);
                   -  + ]
    1657                 :          4 :             continue;
    1658                 :            :         }
    1659                 :            : 
    1660         [ +  + ]:         38 :         if (pl_str_startswith0(shader, "//!PARAM")) {
    1661                 :            :             struct pl_hook_par hp;
    1662         [ -  + ]:         16 :             if (!parse_param(gpu->log, hook, &shader, &hp))
    1663                 :          0 :                 goto error;
    1664                 :            : 
    1665                 :         16 :             PL_INFO(gpu, "Registering named parameter '%s'", hp.name);
    1666   [ +  +  -  +  :         16 :             PL_ARRAY_APPEND(hook, p->hook_params, hp);
                   -  + ]
    1667                 :         16 :             continue;
    1668                 :            :         }
    1669                 :            : 
    1670                 :            :         struct custom_shader_hook h;
    1671         [ -  + ]:         22 :         if (!parse_hook(gpu->log, &shader, &h))
    1672                 :          0 :             goto error;
    1673                 :            : 
    1674                 :         22 :         struct hook_pass pass = {
    1675                 :            :             .exec_stages = 0,
    1676                 :            :             .hook = h,
    1677                 :            :         };
    1678                 :            : 
    1679         [ +  + ]:        374 :         for (int i = 0; i < PL_ARRAY_SIZE(h.hook_tex); i++)
    1680                 :        352 :             pass.exec_stages |= mp_stage_to_pl(h.hook_tex[i]);
    1681         [ +  + ]:        374 :         for (int i = 0; i < PL_ARRAY_SIZE(h.bind_tex); i++) {
    1682                 :        352 :             p->save_stages |= mp_stage_to_pl(h.bind_tex[i]);
    1683         [ +  + ]:        352 :             if (pl_str_equals0(h.bind_tex[i], "HOOKED"))
    1684                 :          8 :                 p->save_stages |= pass.exec_stages;
    1685                 :            :         }
    1686                 :            : 
    1687                 :            :         // As an extra precaution, this avoids errors when trying to run
    1688                 :            :         // conditions against planes that were never hooked. As a sole
    1689                 :            :         // exception, OUTPUT is special because it's hard-coded to return the
    1690                 :            :         // dst_rect even before it was hooked. (This is an apparently
    1691                 :            :         // undocumented mpv quirk, but shaders rely on it in practice)
    1692                 :            :         enum pl_hook_stage rpn_stages = 0;
    1693         [ +  + ]:        726 :         for (int i = 0; i < PL_ARRAY_SIZE(h.width); i++) {
    1694         [ +  + ]:        704 :             if (h.width[i].tag == SHEXP_TEX_W || h.width[i].tag == SHEXP_TEX_H)
    1695                 :         22 :                 rpn_stages |= mp_stage_to_pl(h.width[i].val.varname);
    1696                 :            :         }
    1697         [ +  + ]:        726 :         for (int i = 0; i < PL_ARRAY_SIZE(h.height); i++) {
    1698         [ +  + ]:        704 :             if (h.height[i].tag == SHEXP_TEX_W || h.height[i].tag == SHEXP_TEX_H)
    1699                 :         22 :                 rpn_stages |= mp_stage_to_pl(h.height[i].val.varname);
    1700                 :            :         }
    1701         [ +  + ]:        726 :         for (int i = 0; i < PL_ARRAY_SIZE(h.cond); i++) {
    1702         [ +  + ]:        704 :             if (h.cond[i].tag == SHEXP_TEX_W || h.cond[i].tag == SHEXP_TEX_H)
    1703                 :          8 :                 rpn_stages |= mp_stage_to_pl(h.cond[i].val.varname);
    1704                 :            :         }
    1705                 :            : 
    1706                 :         22 :         p->save_stages |= rpn_stages & ~PL_HOOK_OUTPUT;
    1707                 :            : 
    1708         [ +  - ]:         44 :         PL_INFO(gpu, "Registering hook pass: %.*s", PL_STR_FMT(h.pass_desc));
    1709   [ +  +  -  +  :         22 :         PL_ARRAY_APPEND(hook, p->hook_passes, pass);
                   -  + ]
    1710                 :            :     }
    1711                 :            : 
    1712                 :            :     // We need to hook on both the exec and save stages, so that we can keep
    1713                 :            :     // track of any textures we might need
    1714                 :         10 :     hook->stages |= p->save_stages;
    1715         [ +  + ]:         32 :     for (int i = 0; i < p->hook_passes.num; i++)
    1716                 :         22 :         hook->stages |= p->hook_passes.elem[i].exec_stages;
    1717                 :            : 
    1718                 :         10 :     hook->parameters = p->hook_params.elem;
    1719                 :         10 :     hook->num_parameters = p->hook_params.num;
    1720                 :            : 
    1721                 :         10 :     PL_MSG(gpu, PL_LOG_DEBUG, "Loaded user shader:");
    1722                 :         10 :     pl_msg_source(gpu->log, PL_LOG_DEBUG, shader_text);
    1723                 :            : 
    1724                 :         10 :     return hook;
    1725                 :            : 
    1726                 :          0 : error:
    1727                 :          0 :     pl_mpv_user_shader_destroy((const struct pl_hook **) &hook);
    1728                 :          0 :     PL_MSG(gpu, PL_LOG_ERR, "Failed to parse user shader:");
    1729                 :          0 :     pl_msg_source(gpu->log, PL_LOG_ERR, shader_text);
    1730                 :          0 :     pl_log_stack_trace(gpu->log, PL_LOG_ERR);
    1731                 :          0 :     return NULL;
    1732                 :            : }
    1733                 :            : 
    1734                 :         10 : void pl_mpv_user_shader_destroy(const struct pl_hook **hookp)
    1735                 :            : {
    1736                 :         10 :     const struct pl_hook *hook = *hookp;
    1737         [ +  - ]:         10 :     if (!hook)
    1738                 :            :         return;
    1739                 :            : 
    1740                 :         10 :     struct hook_priv *p = PL_PRIV(hook);
    1741         [ +  + ]:         20 :     for (int i = 0; i < p->descriptors.num; i++) {
    1742   [ +  +  -  - ]:         10 :         switch (p->descriptors.elem[i].desc.type) {
    1743                 :          4 :             case PL_DESC_BUF_UNIFORM:
    1744                 :            :             case PL_DESC_BUF_STORAGE:
    1745                 :            :             case PL_DESC_BUF_TEXEL_UNIFORM:
    1746                 :            :             case PL_DESC_BUF_TEXEL_STORAGE: {
    1747                 :          4 :                 pl_buf buf = p->descriptors.elem[i].binding.object;
    1748                 :          4 :                 pl_buf_destroy(p->gpu, &buf);
    1749                 :            :                 break;
    1750                 :            :             }
    1751                 :            : 
    1752                 :          6 :             case PL_DESC_SAMPLED_TEX:
    1753                 :            :             case PL_DESC_STORAGE_IMG: {
    1754                 :          6 :                 pl_tex tex = p->descriptors.elem[i].binding.object;
    1755                 :          6 :                 pl_tex_destroy(p->gpu, &tex);
    1756                 :            :                 break;
    1757                 :            : 
    1758                 :            :             case PL_DESC_INVALID:
    1759                 :            :             case PL_DESC_TYPE_COUNT:
    1760                 :          0 :                 pl_unreachable();
    1761                 :            :             }
    1762                 :            :         }
    1763                 :            :     }
    1764                 :            : 
    1765                 :         10 :     pl_shader_free(&p->trc_helper);
    1766                 :         10 :     pl_free((void *) hook);
    1767                 :         10 :     *hookp = NULL;
    1768                 :            : }

Generated by: LCOV version 1.16