LCOV - code coverage report
Current view: top level - src - renderer.c (source / functions) Hit Total Coverage
Test: Code coverage Lines: 1408 1778 79.2 %
Date: 2025-03-29 09:04:10 Functions: 49 55 89.1 %
Legend: Lines: hit not hit | Branches: + taken - not taken # not executed Branches: 932 1575 59.2 %

           Branch data     Line data    Source code
       1                 :            : /*
       2                 :            :  * This file is part of libplacebo.
       3                 :            :  *
       4                 :            :  * libplacebo is free software; you can redistribute it and/or
       5                 :            :  * modify it under the terms of the GNU Lesser General Public
       6                 :            :  * License as published by the Free Software Foundation; either
       7                 :            :  * version 2.1 of the License, or (at your option) any later version.
       8                 :            :  *
       9                 :            :  * libplacebo is distributed in the hope that it will be useful,
      10                 :            :  * but WITHOUT ANY WARRANTY; without even the implied warranty of
      11                 :            :  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      12                 :            :  * GNU Lesser General Public License for more details.
      13                 :            :  *
      14                 :            :  * You should have received a copy of the GNU Lesser General Public
      15                 :            :  * License along with libplacebo.  If not, see <http://www.gnu.org/licenses/>.
      16                 :            :  */
      17                 :            : 
      18                 :            : #include <math.h>
      19                 :            : 
      20                 :            : #include "common.h"
      21                 :            : #include "filters.h"
      22                 :            : #include "hash.h"
      23                 :            : #include "shaders.h"
      24                 :            : #include "dispatch.h"
      25                 :            : 
      26                 :            : #include <libplacebo/renderer.h>
      27                 :            : 
      28                 :            : struct cached_frame {
      29                 :            :     uint64_t signature;
      30                 :            :     uint64_t params_hash; // for detecting `pl_render_params` changes
      31                 :            :     struct pl_color_space color;
      32                 :            :     struct pl_icc_profile profile;
      33                 :            :     pl_rect2df crop;
      34                 :            :     pl_tex tex;
      35                 :            :     int comps;
      36                 :            :     bool evict; // for garbage collection
      37                 :            : };
      38                 :            : 
      39                 :            : struct sampler {
      40                 :            :     pl_shader_obj upscaler_state;
      41                 :            :     pl_shader_obj downscaler_state;
      42                 :            : };
      43                 :            : 
      44                 :            : struct osd_vertex {
      45                 :            :     float pos[2];
      46                 :            :     float coord[2];
      47                 :            :     float color[4];
      48                 :            : };
      49                 :            : 
      50                 :            : struct icc_state {
      51                 :            :     pl_icc_object icc;
      52                 :            :     uint64_t error; // set to profile signature on failure
      53                 :            : };
      54                 :            : 
      55                 :            : struct pl_renderer_t {
      56                 :            :     pl_gpu gpu;
      57                 :            :     pl_dispatch dp;
      58                 :            :     pl_log log;
      59                 :            : 
      60                 :            :     // Cached feature checks (inverted)
      61                 :            :     enum pl_render_error errors;
      62                 :            : 
      63                 :            :     // List containing signatures of disabled hooks
      64                 :            :     PL_ARRAY(uint64_t) disabled_hooks;
      65                 :            : 
      66                 :            :     // Shader resource objects and intermediate textures (FBOs)
      67                 :            :     pl_shader_obj tone_map_state;
      68                 :            :     pl_shader_obj dither_state;
      69                 :            :     pl_shader_obj grain_state[4];
      70                 :            :     pl_shader_obj lut_state[3];
      71                 :            :     pl_shader_obj icc_state[2];
      72                 :            :     PL_ARRAY(pl_tex) fbos;
      73                 :            :     struct sampler sampler_main;
      74                 :            :     struct sampler sampler_contrast;
      75                 :            :     struct sampler samplers_src[4];
      76                 :            :     struct sampler samplers_dst[4];
      77                 :            : 
      78                 :            :     // Temporary storage for vertex/index data
      79                 :            :     PL_ARRAY(struct osd_vertex) osd_vertices;
      80                 :            :     PL_ARRAY(uint16_t) osd_indices;
      81                 :            :     struct pl_vertex_attrib osd_attribs[3];
      82                 :            : 
      83                 :            :     // Frame cache (for frame mixing / interpolation)
      84                 :            :     PL_ARRAY(struct cached_frame) frames;
      85                 :            :     PL_ARRAY(pl_tex) frame_fbos;
      86                 :            : 
      87                 :            :     // For debugging / logging purposes
      88                 :            :     int prev_dither;
      89                 :            : 
      90                 :            :     // For backwards compatibility
      91                 :            :     struct icc_state icc_fallback[2];
      92                 :            : };
      93                 :            : 
      94                 :            : enum {
      95                 :            :     // Index into `lut_state`
      96                 :            :     LUT_IMAGE,
      97                 :            :     LUT_TARGET,
      98                 :            :     LUT_PARAMS,
      99                 :            : };
     100                 :            : 
     101                 :            : enum {
     102                 :            :     // Index into `icc_state`
     103                 :            :     ICC_IMAGE,
     104                 :            :     ICC_TARGET
     105                 :            : };
     106                 :            : 
     107                 :          9 : pl_renderer pl_renderer_create(pl_log log, pl_gpu gpu)
     108                 :            : {
     109                 :          9 :     pl_renderer rr = pl_alloc_ptr(NULL, rr);
     110                 :          9 :     *rr = (struct pl_renderer_t) {
     111                 :            :         .gpu  = gpu,
     112                 :            :         .log = log,
     113                 :          9 :         .dp  = pl_dispatch_create(log, gpu),
     114                 :            :         .osd_attribs = {
     115                 :            :             {
     116                 :            :                 .name = "pos",
     117                 :            :                 .offset = offsetof(struct osd_vertex, pos),
     118                 :          9 :                 .fmt = pl_find_vertex_fmt(gpu, PL_FMT_FLOAT, 2),
     119                 :            :             }, {
     120                 :            :                 .name = "coord",
     121                 :            :                 .offset = offsetof(struct osd_vertex, coord),
     122                 :          9 :                 .fmt = pl_find_vertex_fmt(gpu, PL_FMT_FLOAT, 2),
     123                 :            :             }, {
     124                 :            :                 .name = "osd_color",
     125                 :            :                 .offset = offsetof(struct osd_vertex, color),
     126                 :          9 :                 .fmt = pl_find_vertex_fmt(gpu, PL_FMT_FLOAT, 4),
     127                 :            :             }
     128                 :            :         },
     129                 :            :     };
     130                 :            : 
     131         [ -  + ]:          9 :     assert(rr->dp);
     132                 :          9 :     return rr;
     133                 :            : }
     134                 :            : 
     135                 :            : static void sampler_destroy(pl_renderer rr, struct sampler *sampler)
     136                 :            : {
     137                 :         90 :     pl_shader_obj_destroy(&sampler->upscaler_state);
     138                 :         90 :     pl_shader_obj_destroy(&sampler->downscaler_state);
     139                 :            : }
     140                 :            : 
     141                 :          9 : void pl_renderer_destroy(pl_renderer *p_rr)
     142                 :            : {
     143                 :          9 :     pl_renderer rr = *p_rr;
     144         [ +  - ]:          9 :     if (!rr)
     145                 :            :         return;
     146                 :            : 
     147                 :            :     // Free all intermediate FBOs
     148         [ +  + ]:         35 :     for (int i = 0; i < rr->fbos.num; i++)
     149                 :         26 :         pl_tex_destroy(rr->gpu, &rr->fbos.elem[i]);
     150         [ +  + ]:         13 :     for (int i = 0; i < rr->frames.num; i++)
     151                 :          4 :         pl_tex_destroy(rr->gpu, &rr->frames.elem[i].tex);
     152         [ +  + ]:         21 :     for (int i = 0; i < rr->frame_fbos.num; i++)
     153                 :         12 :         pl_tex_destroy(rr->gpu, &rr->frame_fbos.elem[i]);
     154                 :            : 
     155                 :            :     // Free all shader resource objects
     156                 :          9 :     pl_shader_obj_destroy(&rr->tone_map_state);
     157                 :          9 :     pl_shader_obj_destroy(&rr->dither_state);
     158         [ +  + ]:         36 :     for (int i = 0; i < PL_ARRAY_SIZE(rr->lut_state); i++)
     159                 :         27 :         pl_shader_obj_destroy(&rr->lut_state[i]);
     160         [ +  + ]:         45 :     for (int i = 0; i < PL_ARRAY_SIZE(rr->grain_state); i++)
     161                 :         36 :         pl_shader_obj_destroy(&rr->grain_state[i]);
     162         [ +  + ]:         27 :     for (int i = 0; i < PL_ARRAY_SIZE(rr->icc_state); i++)
     163                 :         18 :         pl_shader_obj_destroy(&rr->icc_state[i]);
     164                 :            : 
     165                 :            :     // Free all samplers
     166                 :            :     sampler_destroy(rr, &rr->sampler_main);
     167                 :            :     sampler_destroy(rr, &rr->sampler_contrast);
     168         [ +  + ]:         45 :     for (int i = 0; i < PL_ARRAY_SIZE(rr->samplers_src); i++)
     169                 :            :         sampler_destroy(rr, &rr->samplers_src[i]);
     170         [ +  + ]:         45 :     for (int i = 0; i < PL_ARRAY_SIZE(rr->samplers_dst); i++)
     171                 :            :         sampler_destroy(rr, &rr->samplers_dst[i]);
     172                 :            : 
     173                 :            :     // Free fallback ICC profiles
     174         [ +  + ]:         27 :     for (int i = 0; i < PL_ARRAY_SIZE(rr->icc_fallback); i++)
     175                 :         18 :         pl_icc_close(&rr->icc_fallback[i].icc);
     176                 :            : 
     177                 :          9 :     pl_dispatch_destroy(&rr->dp);
     178                 :          9 :     pl_free_ptr(p_rr);
     179                 :            : }
     180                 :            : 
     181                 :          0 : size_t pl_renderer_save(pl_renderer rr, uint8_t *out)
     182                 :            : {
     183         [ #  # ]:          0 :     return pl_cache_save(pl_gpu_cache(rr->gpu), out, out ? SIZE_MAX : 0);
     184                 :            : }
     185                 :            : 
     186                 :          0 : void pl_renderer_load(pl_renderer rr, const uint8_t *cache)
     187                 :            : {
     188                 :          0 :     pl_cache_load(pl_gpu_cache(rr->gpu), cache, SIZE_MAX);
     189                 :          0 : }
     190                 :            : 
     191                 :          0 : void pl_renderer_flush_cache(pl_renderer rr)
     192                 :            : {
     193         [ #  # ]:          0 :     for (int i = 0; i < rr->frames.num; i++)
     194                 :          0 :         pl_tex_destroy(rr->gpu, &rr->frames.elem[i].tex);
     195                 :          0 :     rr->frames.num = 0;
     196                 :            : 
     197                 :          0 :     pl_reset_detected_peak(rr->tone_map_state);
     198                 :          0 : }
     199                 :            : 
     200                 :            : const struct pl_render_params pl_render_fast_params = { PL_RENDER_DEFAULTS };
     201                 :            : const struct pl_render_params pl_render_default_params = {
     202                 :            :     PL_RENDER_DEFAULTS
     203                 :            :     .upscaler           = &pl_filter_lanczos,
     204                 :            :     .downscaler         = &pl_filter_hermite,
     205                 :            :     .frame_mixer        = &pl_filter_oversample,
     206                 :            :     .sigmoid_params     = &pl_sigmoid_default_params,
     207                 :            :     .dither_params      = &pl_dither_default_params,
     208                 :            :     .peak_detect_params = &pl_peak_detect_default_params,
     209                 :            : };
     210                 :            : 
     211                 :            : const struct pl_render_params pl_render_high_quality_params = {
     212                 :            :     PL_RENDER_DEFAULTS
     213                 :            :     .upscaler           = &pl_filter_ewa_lanczossharp,
     214                 :            :     .downscaler         = &pl_filter_hermite,
     215                 :            :     .frame_mixer        = &pl_filter_oversample,
     216                 :            :     .sigmoid_params     = &pl_sigmoid_default_params,
     217                 :            :     .peak_detect_params = &pl_peak_detect_high_quality_params,
     218                 :            :     .color_map_params   = &pl_color_map_high_quality_params,
     219                 :            :     .dither_params      = &pl_dither_default_params,
     220                 :            :     .deband_params      = &pl_deband_default_params,
     221                 :            : };
     222                 :            : 
     223                 :            : const struct pl_filter_preset pl_frame_mixers[] = {
     224                 :            :     { "none",           NULL,                       "No frame mixing" },
     225                 :            :     { "linear",         &pl_filter_bilinear,        "Linear frame mixing" },
     226                 :            :     { "oversample",     &pl_filter_oversample,      "Oversample (AKA SmoothMotion)" },
     227                 :            :     { "mitchell_clamp", &pl_filter_mitchell_clamp,  "Clamped Mitchell spline" },
     228                 :            :     { "hermite",        &pl_filter_hermite,         "Cubic spline (Hermite)" },
     229                 :            :     {0}
     230                 :            : };
     231                 :            : 
     232                 :            : const int pl_num_frame_mixers = PL_ARRAY_SIZE(pl_frame_mixers) - 1;
     233                 :            : 
     234                 :            : const struct pl_filter_preset pl_scale_filters[] = {
     235                 :            :     {"none",                NULL,                   "Built-in sampling"},
     236                 :            :     {"oversample",          &pl_filter_oversample,  "Oversample (Aspect-preserving NN)"},
     237                 :            :     COMMON_FILTER_PRESETS,
     238                 :            :     {0}
     239                 :            : };
     240                 :            : 
     241                 :            : const int pl_num_scale_filters = PL_ARRAY_SIZE(pl_scale_filters) - 1;
     242                 :            : 
     243                 :            : // Represents a "in-flight" image, which is either a shader that's in the
     244                 :            : // process of producing some sort of image, or a texture that needs to be
     245                 :            : // sampled from
     246                 :            : struct img {
     247                 :            :     // Effective texture size, always set
     248                 :            :     int w, h;
     249                 :            : 
     250                 :            :     // Recommended format (falls back to fbofmt otherwise), only for shaders
     251                 :            :     pl_fmt fmt;
     252                 :            : 
     253                 :            :     // Exactly *one* of these two is set:
     254                 :            :     pl_shader sh;
     255                 :            :     pl_tex tex;
     256                 :            : 
     257                 :            :     // If true, created shaders will be set to unique
     258                 :            :     bool unique;
     259                 :            : 
     260                 :            :     // Information about what to log/disable/fallback to if the shader fails
     261                 :            :     const char *err_msg;
     262                 :            :     enum pl_render_error err_enum;
     263                 :            :     pl_tex err_tex;
     264                 :            : 
     265                 :            :     // Current effective source area, will be sampled by the main scaler
     266                 :            :     pl_rect2df rect;
     267                 :            : 
     268                 :            :     // The current effective colorspace
     269                 :            :     struct pl_color_repr repr;
     270                 :            :     struct pl_color_space color;
     271                 :            :     int comps;
     272                 :            : };
     273                 :            : 
     274                 :            : // Plane 'type', ordered by incrementing priority
     275                 :            : enum plane_type {
     276                 :            :     PLANE_INVALID = 0,
     277                 :            :     PLANE_ALPHA,
     278                 :            :     PLANE_CHROMA,
     279                 :            :     PLANE_LUMA,
     280                 :            :     PLANE_RGB,
     281                 :            :     PLANE_XYZ,
     282                 :            : };
     283                 :            : 
     284                 :       7557 : static inline enum plane_type detect_plane_type(const struct pl_plane *plane,
     285                 :            :                                                 const struct pl_color_repr *repr)
     286                 :            : {
     287         [ +  + ]:       7557 :     if (pl_color_system_is_ycbcr_like(repr->sys)) {
     288                 :            :         int t = PLANE_INVALID;
     289         [ +  + ]:       7306 :         for (int c = 0; c < plane->components; c++) {
     290   [ +  -  +  - ]:       3653 :             switch (plane->component_mapping[c]) {
     291                 :       3643 :             case PL_CHANNEL_Y: t = PL_MAX(t, PLANE_LUMA); continue;
     292                 :          0 :             case PL_CHANNEL_A: t = PL_MAX(t, PLANE_ALPHA); continue;
     293                 :            : 
     294                 :         10 :             case PL_CHANNEL_CB:
     295                 :            :             case PL_CHANNEL_CR:
     296                 :         10 :                 t = PL_MAX(t, PLANE_CHROMA);
     297                 :         10 :                 continue;
     298                 :            : 
     299                 :          0 :             default: continue;
     300                 :            :             }
     301                 :            :         }
     302                 :            : 
     303         [ -  + ]:       3653 :         pl_assert(t);
     304                 :       3653 :         return t;
     305                 :            :     }
     306                 :            : 
     307                 :            :     // Extra test for exclusive / separated alpha plane
     308         [ +  - ]:       3904 :     if (plane->components == 1 && plane->component_mapping[0] == PL_CHANNEL_A)
     309                 :            :         return PLANE_ALPHA;
     310                 :            : 
     311      [ -  -  + ]:       3904 :     switch (repr->sys) {
     312                 :            :     case PL_COLOR_SYSTEM_UNKNOWN: // fall through to RGB
     313                 :            :     case PL_COLOR_SYSTEM_RGB: return PLANE_RGB;
     314                 :          0 :     case PL_COLOR_SYSTEM_XYZ: return PLANE_XYZ;
     315                 :            : 
     316                 :            :     // For the switch completeness check
     317                 :            :     case PL_COLOR_SYSTEM_BT_601:
     318                 :            :     case PL_COLOR_SYSTEM_BT_709:
     319                 :            :     case PL_COLOR_SYSTEM_SMPTE_240M:
     320                 :            :     case PL_COLOR_SYSTEM_BT_2020_NC:
     321                 :            :     case PL_COLOR_SYSTEM_BT_2020_C:
     322                 :            :     case PL_COLOR_SYSTEM_BT_2100_PQ:
     323                 :            :     case PL_COLOR_SYSTEM_BT_2100_HLG:
     324                 :            :     case PL_COLOR_SYSTEM_DOLBYVISION:
     325                 :            :     case PL_COLOR_SYSTEM_YCGCO:
     326                 :            :     case PL_COLOR_SYSTEM_COUNT:
     327                 :            :         break;
     328                 :            :     }
     329                 :            : 
     330                 :          0 :     pl_unreachable();
     331                 :            : }
     332                 :            : 
     333                 :            : struct pass_state {
     334                 :            :     void *tmp;
     335                 :            :     pl_renderer rr;
     336                 :            :     const struct pl_render_params *params;
     337                 :            :     struct pl_render_info info; // for info callback
     338                 :            : 
     339                 :            :     // Represents the "current" image which we're in the process of rendering.
     340                 :            :     // This is initially set by pass_read_image, and all of the subsequent
     341                 :            :     // rendering steps will mutate this in-place.
     342                 :            :     struct img img;
     343                 :            : 
     344                 :            :     // Represents the "reference rect". Canonically, this is functionally
     345                 :            :     // equivalent to `image.crop`, but also updates as the refplane evolves
     346                 :            :     // (e.g. due to user hook prescalers)
     347                 :            :     pl_rect2df ref_rect;
     348                 :            : 
     349                 :            :     // Integer version of `target.crop`. Semantically identical.
     350                 :            :     pl_rect2d dst_rect;
     351                 :            : 
     352                 :            :     // Logical end-to-end rotation
     353                 :            :     pl_rotation rotation;
     354                 :            : 
     355                 :            :     // Cached copies of the `image` / `target` for this rendering pass,
     356                 :            :     // corrected to make sure all rects etc. are properly defaulted/inferred.
     357                 :            :     struct pl_frame image;
     358                 :            :     struct pl_frame target;
     359                 :            : 
     360                 :            :     // Cached copies of the `prev` / `next` frames, for deinterlacing.
     361                 :            :     struct pl_frame prev, next;
     362                 :            : 
     363                 :            :     // Some extra plane metadata, inferred from `planes`
     364                 :            :     enum plane_type src_type[4];
     365                 :            :     int src_ref, dst_ref; // index into `planes`
     366                 :            : 
     367                 :            :     // Metadata for `rr->fbos`
     368                 :            :     pl_fmt fbofmt[5];
     369                 :            :     bool *fbos_used;
     370                 :            :     bool need_peak_fbo; // need indirection for peak detection
     371                 :            : 
     372                 :            :     // Map of acquired frames
     373                 :            :     struct {
     374                 :            :         bool target, image, prev, next;
     375                 :            :     } acquired;
     376                 :            : };
     377                 :            : 
     378                 :       1409 : static void find_fbo_format(struct pass_state *pass)
     379                 :            : {
     380                 :       1409 :     const struct pl_render_params *params = pass->params;
     381                 :       1409 :     pl_renderer rr = pass->rr;
     382   [ +  +  +  -  :       1409 :     if (params->disable_fbos || (rr->errors & PL_RENDER_ERR_FBO) || pass->fbofmt[4])
                   +  + ]
     383                 :       1409 :         return;
     384                 :            : 
     385                 :            :     struct {
     386                 :            :         enum pl_fmt_type type;
     387                 :            :         int depth;
     388                 :            :         enum pl_fmt_caps caps;
     389                 :       1085 :     } configs[] = {
     390                 :            :         // Prefer floating point formats first
     391                 :            :         {PL_FMT_FLOAT, 16, PL_FMT_CAP_LINEAR},
     392                 :            :         {PL_FMT_FLOAT, 16, PL_FMT_CAP_SAMPLEABLE},
     393                 :            : 
     394                 :            :         // Otherwise, fall back to unorm/snorm, preferring linearly sampleable
     395                 :            :         {PL_FMT_UNORM, 16, PL_FMT_CAP_LINEAR},
     396                 :            :         {PL_FMT_SNORM, 16, PL_FMT_CAP_LINEAR},
     397                 :            :         {PL_FMT_UNORM, 16, PL_FMT_CAP_SAMPLEABLE},
     398                 :            :         {PL_FMT_SNORM, 16, PL_FMT_CAP_SAMPLEABLE},
     399                 :            : 
     400                 :            :         // As a final fallback, allow 8-bit FBO formats (for UNORM only)
     401                 :            :         {PL_FMT_UNORM, 8, PL_FMT_CAP_LINEAR},
     402                 :            :         {PL_FMT_UNORM, 8, PL_FMT_CAP_SAMPLEABLE},
     403                 :            :     };
     404                 :            : 
     405                 :            :     pl_fmt fmt = NULL;
     406         [ +  - ]:       1085 :     for (int i = 0; i < PL_ARRAY_SIZE(configs); i++) {
     407   [ -  +  -  - ]:       1085 :         if (params->force_low_bit_depth_fbos && configs[i].depth > 8)
     408                 :          0 :             continue;
     409                 :            : 
     410                 :       1085 :         fmt = pl_find_fmt(rr->gpu, configs[i].type, 4, configs[i].depth, 0,
     411                 :       1085 :                           PL_FMT_CAP_RENDERABLE | configs[i].caps);
     412         [ -  + ]:       1085 :         if (!fmt)
     413                 :          0 :             continue;
     414                 :            : 
     415                 :       1085 :         pass->fbofmt[4] = fmt;
     416                 :            : 
     417                 :            :         // Probe the right variant for each number of channels, falling
     418                 :            :         // back to the next biggest format
     419         [ +  + ]:       4340 :         for (int c = 1; c < 4; c++) {
     420                 :       6510 :             pass->fbofmt[c] = pl_find_fmt(rr->gpu, configs[i].type, c,
     421                 :       3255 :                                         configs[i].depth, 0, fmt->caps);
     422         [ +  + ]:       3255 :             pass->fbofmt[c] = PL_DEF(pass->fbofmt[c], pass->fbofmt[c+1]);
     423                 :            :         }
     424                 :            :         return;
     425                 :            :     }
     426                 :            : 
     427                 :          0 :     PL_WARN(rr, "Found no renderable FBO format! Most features disabled");
     428                 :          0 :     rr->errors |= PL_RENDER_ERR_FBO;
     429                 :            : }
     430                 :            : 
     431                 :       1803 : static void info_callback(void *priv, const struct pl_dispatch_info *dinfo)
     432                 :            : {
     433                 :            :     struct pass_state *pass = priv;
     434                 :       1803 :     const struct pl_render_params *params = pass->params;
     435         [ +  + ]:       1803 :     if (!params->info_callback)
     436                 :            :         return;
     437                 :            : 
     438                 :        928 :     pass->info.pass = dinfo;
     439                 :        928 :     params->info_callback(params->info_priv, &pass->info);
     440                 :        928 :     pass->info.index++;
     441                 :            : }
     442                 :            : 
     443                 :        380 : static pl_tex get_fbo(struct pass_state *pass, int w, int h, pl_fmt fmt,
     444                 :            :                       int comps, pl_debug_tag debug_tag)
     445                 :            : {
     446                 :        380 :     pl_renderer rr = pass->rr;
     447         [ -  + ]:        380 :     comps = PL_DEF(comps, 4);
     448         [ +  + ]:        380 :     fmt = PL_DEF(fmt, pass->fbofmt[comps]);
     449         [ -  + ]:        375 :     if (!fmt)
     450                 :            :         return NULL;
     451                 :            : 
     452                 :        380 :     struct pl_tex_params params = {
     453                 :            :         .w          = w,
     454                 :            :         .h          = h,
     455                 :            :         .format     = fmt,
     456                 :            :         .sampleable = true,
     457                 :            :         .renderable = true,
     458                 :        380 :         .blit_src   = fmt->caps & PL_FMT_CAP_BLITTABLE,
     459                 :        380 :         .storable   = fmt->caps & PL_FMT_CAP_STORABLE,
     460                 :            :         .debug_tag  = debug_tag,
     461                 :            :     };
     462                 :            : 
     463                 :            :     int best_idx = -1;
     464                 :            :     int best_diff = 0;
     465                 :            : 
     466                 :            :     // Find the best-fitting texture out of rr->fbos
     467         [ +  + ]:       1137 :     for (int i = 0; i < rr->fbos.num; i++) {
     468         [ +  + ]:        757 :         if (pass->fbos_used[i])
     469                 :        147 :             continue;
     470                 :            : 
     471                 :            :         // Orthogonal distance, with penalty for format mismatches
     472                 :       1220 :         int diff = abs(rr->fbos.elem[i]->params.w - w) +
     473                 :        610 :                    abs(rr->fbos.elem[i]->params.h - h) +
     474         [ +  + ]:        610 :                    ((rr->fbos.elem[i]->params.format != fmt) ? 1000 : 0);
     475                 :            : 
     476         [ +  + ]:        610 :         if (best_idx < 0 || diff < best_diff) {
     477                 :            :             best_idx = i;
     478                 :            :             best_diff = diff;
     479                 :            :         }
     480                 :            :     }
     481                 :            : 
     482                 :            :     // No texture found at all, add a new one
     483         [ +  + ]:        380 :     if (best_idx < 0) {
     484                 :            :         best_idx = rr->fbos.num;
     485   [ +  +  -  +  :         26 :         PL_ARRAY_APPEND(rr, rr->fbos, NULL);
                   -  + ]
     486         [ +  - ]:         26 :         pl_grow(pass->tmp, &pass->fbos_used, rr->fbos.num * sizeof(bool));
     487                 :         26 :         pass->fbos_used[best_idx] = false;
     488                 :            :     }
     489                 :            : 
     490         [ -  + ]:        380 :     if (!pl_tex_recreate(rr->gpu, &rr->fbos.elem[best_idx], &params))
     491                 :            :         return NULL;
     492                 :            : 
     493                 :        380 :     pass->fbos_used[best_idx] = true;
     494                 :        380 :     return rr->fbos.elem[best_idx];
     495                 :            : }
     496                 :            : 
     497                 :            : // Forcibly convert an img to `tex`, dispatching where necessary
     498                 :       1176 : static pl_tex _img_tex(struct pass_state *pass, struct img *img, pl_debug_tag tag)
     499                 :            : {
     500         [ +  + ]:       1176 :     if (img->tex) {
     501         [ -  + ]:        814 :         pl_assert(!img->sh);
     502                 :            :         return img->tex;
     503                 :            :     }
     504                 :            : 
     505                 :        362 :     pl_renderer rr = pass->rr;
     506                 :        362 :     pl_tex tex = get_fbo(pass, img->w, img->h, img->fmt, img->comps, tag);
     507                 :        362 :     img->fmt = NULL;
     508                 :            : 
     509         [ -  + ]:        362 :     if (!tex) {
     510                 :          0 :         PL_ERR(rr, "Failed creating FBO texture! Disabling advanced rendering..");
     511                 :          0 :         memset(pass->fbofmt, 0, sizeof(pass->fbofmt));
     512                 :          0 :         pl_dispatch_abort(rr->dp, &img->sh);
     513                 :          0 :         rr->errors |= PL_RENDER_ERR_FBO;
     514                 :          0 :         return img->err_tex;
     515                 :            :     }
     516                 :            : 
     517         [ -  + ]:        362 :     pl_assert(img->sh);
     518                 :        362 :     bool ok = pl_dispatch_finish(rr->dp, pl_dispatch_params(
     519                 :            :         .shader = &img->sh,
     520                 :            :         .target = tex,
     521                 :            :     ));
     522                 :            : 
     523                 :        362 :     const char *err_msg = img->err_msg;
     524                 :        362 :     enum pl_render_error err_enum = img->err_enum;
     525                 :        362 :     pl_tex err_tex = img->err_tex;
     526                 :        362 :     img->err_msg = NULL;
     527                 :        362 :     img->err_enum = PL_RENDER_ERR_NONE;
     528                 :        362 :     img->err_tex = NULL;
     529                 :            : 
     530         [ -  + ]:        362 :     if (!ok) {
     531         [ #  # ]:          0 :         PL_ERR(rr, "%s", PL_DEF(err_msg, "Failed dispatching intermediate pass!"));
     532                 :          0 :         rr->errors |= err_enum;
     533                 :          0 :         img->sh = pl_dispatch_begin(rr->dp);
     534                 :          0 :         img->tex = err_tex;
     535                 :          0 :         return img->tex;
     536                 :            :     }
     537                 :            : 
     538                 :        362 :     img->tex = tex;
     539                 :        362 :     return img->tex;
     540                 :            : }
     541                 :            : 
     542                 :            : #define img_tex(pass, img) _img_tex(pass, img, PL_DEBUG_TAG)
     543                 :            : 
     544                 :            : // Forcibly convert an img to `sh`, sampling where necessary
     545                 :       6810 : static pl_shader img_sh(struct pass_state *pass, struct img *img)
     546                 :            : {
     547         [ +  + ]:       6810 :     if (img->sh) {
     548         [ -  + ]:       6786 :         pl_assert(!img->tex);
     549                 :            :         return img->sh;
     550                 :            :     }
     551                 :            : 
     552         [ -  + ]:         24 :     pl_assert(img->tex);
     553                 :         24 :     img->sh = pl_dispatch_begin_ex(pass->rr->dp, img->unique);
     554                 :         24 :     pl_shader_sample_direct(img->sh, pl_sample_src( .tex = img->tex ));
     555                 :            : 
     556                 :         24 :     img->tex = NULL;
     557                 :         24 :     return img->sh;
     558                 :            : }
     559                 :            : 
     560                 :            : enum sampler_type {
     561                 :            :     SAMPLER_DIRECT,     // pick based on texture caps
     562                 :            :     SAMPLER_NEAREST,    // direct sampling, force nearest
     563                 :            :     SAMPLER_BICUBIC,    // fast bicubic scaling
     564                 :            :     SAMPLER_HERMITE,    // fast hermite scaling
     565                 :            :     SAMPLER_GAUSSIAN,   // fast gaussian scaling
     566                 :            :     SAMPLER_COMPLEX,    // complex custom filters
     567                 :            :     SAMPLER_OVERSAMPLE,
     568                 :            : };
     569                 :            : 
     570                 :            : enum sampler_dir {
     571                 :            :     SAMPLER_NOOP, // 1:1 scaling
     572                 :            :     SAMPLER_UP,   // upscaling
     573                 :            :     SAMPLER_DOWN, // downscaling
     574                 :            : };
     575                 :            : 
     576                 :            : enum sampler_usage {
     577                 :            :     SAMPLER_MAIN,
     578                 :            :     SAMPLER_PLANE,
     579                 :            :     SAMPLER_CONTRAST,
     580                 :            : };
     581                 :            : 
     582                 :            : struct sampler_info {
     583                 :            :     const struct pl_filter_config *config; // if applicable
     584                 :            :     enum sampler_usage usage;
     585                 :            :     enum sampler_type type;
     586                 :            :     enum sampler_dir dir;
     587                 :            :     enum sampler_dir dir_sep[2];
     588                 :            : };
     589                 :            : 
     590                 :       2565 : static struct sampler_info sample_src_info(struct pass_state *pass,
     591                 :            :                                            const struct pl_sample_src *src,
     592                 :            :                                            enum sampler_usage usage)
     593                 :            : {
     594                 :       2565 :     const struct pl_render_params *params = pass->params;
     595                 :            :     struct sampler_info info = { .usage = usage };
     596                 :       2565 :     pl_renderer rr = pass->rr;
     597                 :            : 
     598                 :       2565 :     float rx = src->new_w / fabsf(pl_rect_w(src->rect));
     599         [ +  + ]:       2565 :     if (rx < 1.0 - 1e-6) {
     600                 :            :         info.dir_sep[0] = SAMPLER_DOWN;
     601         [ +  + ]:       2343 :     } else if (rx > 1.0 + 1e-6) {
     602                 :            :         info.dir_sep[0] = SAMPLER_UP;
     603                 :            :     }
     604                 :            : 
     605                 :       2565 :     float ry = src->new_h / fabsf(pl_rect_h(src->rect));
     606         [ +  + ]:       2565 :     if (ry < 1.0 - 1e-6) {
     607                 :            :         info.dir_sep[1] = SAMPLER_DOWN;
     608         [ +  + ]:       2343 :     } else if (ry > 1.0 + 1e-6) {
     609                 :            :         info.dir_sep[1] = SAMPLER_UP;
     610                 :            :     }
     611                 :            : 
     612         [ -  + ]:       2565 :     if (params->correct_subpixel_offsets) {
     613   [ #  #  #  # ]:          0 :         if (!info.dir_sep[0] && fabsf(src->rect.x0) > 1e-6f)
     614                 :            :             info.dir_sep[0] = SAMPLER_UP;
     615   [ #  #  #  # ]:          0 :         if (!info.dir_sep[1] && fabsf(src->rect.y0) > 1e-6f)
     616                 :            :             info.dir_sep[1] = SAMPLER_UP;
     617                 :            :     }
     618                 :            : 
     619                 :            :     // We use PL_MAX so downscaling overrides upscaling when choosing scalers
     620                 :       2565 :     info.dir = PL_MAX(info.dir_sep[0], info.dir_sep[1]);
     621      [ +  +  + ]:       2565 :     switch (info.dir) {
     622                 :        222 :     case SAMPLER_DOWN:
     623         [ +  - ]:        222 :         if (usage == SAMPLER_CONTRAST) {
     624                 :            :             info.config = &pl_filter_bicubic;
     625   [ +  +  +  - ]:        222 :         } else if (usage == SAMPLER_PLANE && params->plane_downscaler) {
     626                 :            :             info.config = params->plane_downscaler;
     627                 :            :         } else {
     628                 :        222 :             info.config = params->downscaler;
     629                 :            :         }
     630                 :            :         break;
     631                 :        209 :     case SAMPLER_UP:
     632   [ +  +  +  - ]:        209 :         if (usage == SAMPLER_PLANE && params->plane_upscaler) {
     633                 :            :             info.config = params->plane_upscaler;
     634                 :            :         } else {
     635         [ -  + ]:        209 :             pl_assert(usage != SAMPLER_CONTRAST);
     636                 :        209 :             info.config = params->upscaler;
     637                 :            :         }
     638                 :            :         break;
     639                 :       2134 :     case SAMPLER_NOOP:
     640                 :            :         info.type = SAMPLER_NEAREST;
     641                 :       2134 :         return info;
     642                 :            :     }
     643                 :            : 
     644   [ +  -  +  + ]:        431 :     if ((rr->errors & PL_RENDER_ERR_SAMPLING) || !info.config) {
     645                 :            :         info.type = SAMPLER_DIRECT;
     646         [ +  + ]:        408 :     } else if (info.config->kernel == &pl_filter_function_oversample) {
     647                 :            :         info.type = SAMPLER_OVERSAMPLE;
     648                 :            :     } else {
     649                 :            :         info.type = SAMPLER_COMPLEX;
     650                 :            : 
     651                 :            :         // Try using faster replacements for GPU built-in scalers
     652         [ +  + ]:        392 :         pl_fmt texfmt = src->tex ? src->tex->params.format : pass->fbofmt[4];
     653                 :        392 :         bool can_linear = texfmt->caps & PL_FMT_CAP_LINEAR;
     654   [ +  +  -  + ]:        392 :         bool can_fast = info.dir == SAMPLER_UP || params->skip_anti_aliasing;
     655                 :            : 
     656         [ +  - ]:        192 :         if (can_fast && !params->disable_builtin_scalers) {
     657   [ +  -  +  + ]:        192 :             if (can_linear && pl_filter_config_eq(info.config, &pl_filter_bicubic))
     658                 :            :                 info.type = SAMPLER_BICUBIC;
     659         [ +  + ]:        192 :             if (can_linear && pl_filter_config_eq(info.config, &pl_filter_hermite))
     660                 :            :                 info.type = SAMPLER_HERMITE;
     661         [ +  + ]:        192 :             if (can_linear && pl_filter_config_eq(info.config, &pl_filter_gaussian))
     662                 :            :                 info.type = SAMPLER_GAUSSIAN;
     663         [ +  + ]:        192 :             if (can_linear && pl_filter_config_eq(info.config, &pl_filter_bilinear))
     664                 :            :                 info.type = SAMPLER_DIRECT;
     665         [ +  + ]:        192 :             if (pl_filter_config_eq(info.config, &pl_filter_nearest))
     666                 :          8 :                 info.type = can_linear ? SAMPLER_NEAREST : SAMPLER_DIRECT;
     667                 :            :         }
     668                 :            :     }
     669                 :            : 
     670                 :            :     // Disable advanced scaling without FBOs
     671   [ +  -  -  - ]:        431 :     if (!pass->fbofmt[4] && info.type == SAMPLER_COMPLEX)
     672                 :            :         info.type = SAMPLER_DIRECT;
     673                 :            : 
     674                 :        431 :     return info;
     675                 :            : }
     676                 :            : 
     677                 :        995 : static void dispatch_sampler(struct pass_state *pass, pl_shader sh,
     678                 :            :                              struct sampler *sampler, enum sampler_usage usage,
     679                 :            :                              pl_tex target_tex, const struct pl_sample_src *src)
     680                 :            : {
     681                 :        995 :     const struct pl_render_params *params = pass->params;
     682         [ -  + ]:        995 :     if (!sampler)
     683                 :          0 :         goto fallback;
     684                 :            : 
     685                 :        995 :     pl_renderer rr = pass->rr;
     686                 :        995 :     struct sampler_info info = sample_src_info(pass, src, usage);
     687                 :            :     pl_shader_obj *lut = NULL;
     688   [ +  +  +  - ]:        995 :     switch (info.dir) {
     689                 :        780 :     case SAMPLER_NOOP:
     690                 :        780 :         goto fallback;
     691                 :        114 :     case SAMPLER_DOWN:
     692                 :        114 :         lut = &sampler->downscaler_state;
     693                 :        114 :         break;
     694                 :        101 :     case SAMPLER_UP:
     695                 :        101 :         lut = &sampler->upscaler_state;
     696                 :        101 :         break;
     697                 :            :     }
     698                 :            : 
     699   [ +  +  +  +  :        215 :     switch (info.type) {
                +  +  + ]
     700                 :         15 :     case SAMPLER_DIRECT:
     701                 :         15 :         goto fallback;
     702                 :          4 :     case SAMPLER_NEAREST:
     703                 :          4 :         pl_shader_sample_nearest(sh, src);
     704                 :        200 :         return;
     705                 :          8 :     case SAMPLER_OVERSAMPLE:
     706                 :          8 :         pl_shader_sample_oversample(sh, src, info.config->kernel->params[0]);
     707                 :          8 :         return;
     708                 :          4 :     case SAMPLER_BICUBIC:
     709                 :          4 :         pl_shader_sample_bicubic(sh, src);
     710                 :          4 :         return;
     711                 :          4 :     case SAMPLER_HERMITE:
     712                 :          4 :         pl_shader_sample_hermite(sh, src);
     713                 :          4 :         return;
     714                 :          4 :     case SAMPLER_GAUSSIAN:
     715                 :          4 :         pl_shader_sample_gaussian(sh, src);
     716                 :          4 :         return;
     717                 :            :     case SAMPLER_COMPLEX:
     718                 :            :         break; // continue below
     719                 :            :     }
     720                 :            : 
     721         [ -  + ]:        176 :     pl_assert(lut);
     722                 :        352 :     struct pl_sample_filter_params fparams = {
     723                 :            :         .filter      = *info.config,
     724                 :        176 :         .antiring    = params->antiringing_strength,
     725   [ -  +  -  - ]:        176 :         .no_widening = params->skip_anti_aliasing && usage != SAMPLER_CONTRAST,
     726                 :            :         .lut         = lut,
     727                 :            :     };
     728                 :            : 
     729         [ -  + ]:        176 :     if (target_tex) {
     730                 :          0 :         fparams.no_compute = !target_tex->params.storable;
     731                 :            :     } else {
     732                 :        176 :         fparams.no_compute = !(pass->fbofmt[4]->caps & PL_FMT_CAP_STORABLE);
     733                 :            :     }
     734                 :            : 
     735                 :            :     bool ok;
     736         [ +  + ]:        176 :     if (info.config->polar) {
     737                 :            :         // Polar samplers are always a single function call
     738                 :         72 :         ok = pl_shader_sample_polar(sh, src, &fparams);
     739   [ +  -  +  - ]:        104 :     } else if (info.dir_sep[0] && info.dir_sep[1]) {
     740                 :            :         // Scaling is needed in both directions
     741                 :        104 :         struct pl_sample_src src1 = *src, src2 = *src;
     742                 :        104 :         src1.new_w = src->tex->params.w;
     743                 :        104 :         src1.rect.x0 = 0;
     744                 :        104 :         src1.rect.x1 = src1.new_w;;
     745                 :        104 :         src2.rect.y0 = 0;
     746                 :        104 :         src2.rect.y1 = src1.new_h;
     747                 :            : 
     748                 :        104 :         pl_shader tsh = pl_dispatch_begin(rr->dp);
     749                 :        104 :         ok = pl_shader_sample_ortho2(tsh, &src1, &fparams);
     750         [ -  + ]:        104 :         if (!ok) {
     751                 :          0 :             pl_dispatch_abort(rr->dp, &tsh);
     752                 :          0 :             goto done;
     753                 :            :         }
     754                 :            : 
     755                 :        104 :         struct img img = {
     756                 :            :             .sh = tsh,
     757                 :        104 :             .w  = src1.new_w,
     758                 :        104 :             .h  = src1.new_h,
     759                 :        104 :             .comps = src->components,
     760                 :            :         };
     761                 :            : 
     762                 :        104 :         src2.tex = img_tex(pass, &img);
     763                 :        104 :         src2.scale = 1.0;
     764   [ +  -  +  - ]:        104 :         ok = src2.tex && pl_shader_sample_ortho2(sh, &src2, &fparams);
     765                 :            :     } else {
     766                 :            :         // Scaling is needed only in one direction
     767                 :          0 :         ok = pl_shader_sample_ortho2(sh, src, &fparams);
     768                 :            :     }
     769                 :            : 
     770                 :         72 : done:
     771         [ -  + ]:         72 :     if (!ok) {
     772                 :          0 :         PL_ERR(rr, "Failed dispatching scaler.. disabling");
     773                 :          0 :         rr->errors |= PL_RENDER_ERR_SAMPLING;
     774                 :          0 :         goto fallback;
     775                 :            :     }
     776                 :            : 
     777                 :            :     return;
     778                 :            : 
     779                 :        795 : fallback:
     780                 :            :     // If all else fails, fall back to auto sampling
     781                 :        795 :     pl_shader_sample_direct(sh, src);
     782                 :            : }
     783                 :            : 
     784                 :       1103 : static void swizzle_color(pl_shader sh, int comps, const int comp_map[4],
     785                 :            :                           bool force_alpha)
     786                 :            : {
     787                 :       1103 :     ident_t orig = sh_fresh(sh, "orig_color");
     788                 :       1103 :     GLSL("vec4 "$" = color;                 \n"
     789                 :            :          "color = vec4(0.0, 0.0, 0.0, 1.0); \n", orig);
     790                 :            : 
     791                 :            :     static const int def_map[4] = {0, 1, 2, 3};
     792         [ -  + ]:       1103 :     comp_map = PL_DEF(comp_map, def_map);
     793                 :            : 
     794         [ +  + ]:       4382 :     for (int c = 0; c < comps; c++) {
     795         [ +  - ]:       3279 :         if (comp_map[c] >= 0)
     796                 :       3279 :             GLSL("color[%d] = "$"[%d]; \n", c, orig, comp_map[c]);
     797                 :            :     }
     798                 :            : 
     799         [ +  + ]:       1103 :     if (force_alpha)
     800                 :         16 :         GLSL("color.a = "$".a; \n", orig);
     801                 :       1103 : }
     802                 :            : 
     803                 :            : // `scale` adapts from `pass->dst_rect` to the plane being rendered to
     804                 :       1898 : static void draw_overlays(struct pass_state *pass, pl_tex fbo,
     805                 :            :                           int comps, const int comp_map[4],
     806                 :            :                           const struct pl_overlay *overlays, int num,
     807                 :            :                           struct pl_color_space color, struct pl_color_repr repr,
     808                 :            :                           const pl_transform2x2 *output_shift)
     809                 :            : {
     810                 :       1898 :     pl_renderer rr = pass->rr;
     811   [ +  +  +  - ]:       1898 :     if (num <= 0 || (rr->errors & PL_RENDER_ERR_OVERLAY))
     812                 :       1882 :         return;
     813                 :            : 
     814                 :         16 :     enum pl_fmt_caps caps = fbo->params.format->caps;
     815         [ +  - ]:         16 :     if (!(rr->errors & PL_RENDER_ERR_BLENDING) &&
     816         [ -  + ]:         16 :         !(caps & PL_FMT_CAP_BLENDABLE))
     817                 :            :     {
     818                 :          0 :         PL_WARN(rr, "Trying to draw an overlay to a non-blendable target. "
     819                 :            :                 "Alpha blending is disabled, results may be incorrect!");
     820                 :          0 :         rr->errors |= PL_RENDER_ERR_BLENDING;
     821                 :            :     }
     822                 :            : 
     823         [ +  + ]:         16 :     const struct pl_frame *image = pass->src_ref >= 0 ? &pass->image : NULL;
     824                 :            :     pl_transform2x2 src_to_dst;
     825                 :            :     if (image) {
     826                 :         12 :         float rx = pl_rect_w(pass->dst_rect) / pl_rect_w(image->crop);
     827                 :         12 :         float ry = pl_rect_h(pass->dst_rect) / pl_rect_h(image->crop);
     828                 :         12 :         src_to_dst = (pl_transform2x2) {
     829                 :            :             .mat.m = {{ rx, 0 }, { 0, ry }},
     830                 :            :             .c = {
     831                 :         12 :                 pass->dst_rect.x0 - rx * image->crop.x0,
     832                 :         12 :                 pass->dst_rect.y0 - ry * image->crop.y0,
     833                 :            :             },
     834                 :            :         };
     835                 :            : 
     836         [ -  + ]:         12 :         if (pass->rotation % PL_ROTATION_180 == PL_ROTATION_90) {
     837                 :          0 :             PL_SWAP(src_to_dst.c[0], src_to_dst.c[1]);
     838                 :          0 :             src_to_dst.mat = (pl_matrix2x2) {{{ 0, ry }, { rx, 0 }}};
     839                 :            :         }
     840                 :            :     }
     841                 :            : 
     842                 :            :     const struct pl_frame *target = &pass->target;
     843                 :         16 :     pl_rect2df dst_crop = target->crop;
     844                 :         16 :     pl_rect2df_rotate(&dst_crop, -pass->rotation);
     845                 :         16 :     pl_rect2df_normalize(&dst_crop);
     846                 :            : 
     847         [ +  + ]:         32 :     for (int n = 0; n < num; n++) {
     848                 :         16 :         struct pl_overlay ol = overlays[n];
     849         [ -  + ]:         16 :         if (!ol.num_parts)
     850                 :          0 :             continue;
     851                 :            : 
     852         [ +  - ]:         16 :         if (!ol.coords) {
     853                 :         16 :             ol.coords = overlays == target->overlays
     854                 :            :                             ? PL_OVERLAY_COORDS_DST_FRAME
     855         [ +  + ]:         16 :                             : PL_OVERLAY_COORDS_SRC_FRAME;
     856                 :            :         }
     857                 :            : 
     858                 :         16 :         pl_transform2x2 tf = pl_transform2x2_identity;
     859   [ -  +  -  -  :         16 :         switch (ol.coords) {
                      + ]
     860                 :          0 :             case PL_OVERLAY_COORDS_SRC_CROP:
     861         [ #  # ]:          0 :                 if (!image)
     862                 :          0 :                     continue;
     863                 :          0 :                 tf.c[0] = image->crop.x0;
     864                 :          0 :                 tf.c[1] = image->crop.y0;
     865                 :            :                 // fall through
     866                 :          8 :             case PL_OVERLAY_COORDS_SRC_FRAME:
     867         [ -  + ]:          8 :                 if (!image)
     868                 :          0 :                     continue;
     869                 :          8 :                 pl_transform2x2_rmul(&src_to_dst, &tf);
     870                 :          8 :                 break;
     871                 :          0 :             case PL_OVERLAY_COORDS_DST_CROP:
     872                 :          0 :                 tf.c[0] = dst_crop.x0;
     873                 :          0 :                 tf.c[1] = dst_crop.y0;
     874                 :          0 :                 break;
     875                 :            :             case PL_OVERLAY_COORDS_DST_FRAME:
     876                 :            :                 break;
     877                 :            :             case PL_OVERLAY_COORDS_AUTO:
     878                 :            :             case PL_OVERLAY_COORDS_COUNT:
     879                 :          0 :                 pl_unreachable();
     880                 :            :         }
     881                 :            : 
     882         [ +  - ]:         16 :         if (output_shift)
     883                 :         16 :             pl_transform2x2_rmul(output_shift, &tf);
     884                 :            : 
     885                 :            :         // Construct vertex/index buffers
     886                 :         16 :         rr->osd_vertices.num = 0;
     887                 :         16 :         rr->osd_indices.num = 0;
     888         [ +  + ]:         40 :         for (int i = 0; i < ol.num_parts; i++) {
     889                 :         24 :             const struct pl_overlay_part *part = &ol.parts[i];
     890                 :            : 
     891                 :            : #define EMIT_VERT(x, y)                                                         \
     892                 :            :             do {                                                                \
     893                 :            :                 float pos[2] = { part->dst.x, part->dst.y };                    \
     894                 :            :                 pl_transform2x2_apply(&tf, pos);                                \
     895                 :            :                 PL_ARRAY_APPEND(rr, rr->osd_vertices, (struct osd_vertex) {     \
     896                 :            :                     .pos = {                                                    \
     897                 :            :                         2.0 * (pos[0] / fbo->params.w) - 1.0,                   \
     898                 :            :                         2.0 * (pos[1] / fbo->params.h) - 1.0,                   \
     899                 :            :                     },                                                          \
     900                 :            :                     .coord = {                                                  \
     901                 :            :                         part->src.x / ol.tex->params.w,                         \
     902                 :            :                         part->src.y / ol.tex->params.h,                         \
     903                 :            :                     },                                                          \
     904                 :            :                     .color = {                                                  \
     905                 :            :                         part->color[0], part->color[1],                         \
     906                 :            :                         part->color[2], part->color[3],                         \
     907                 :            :                     },                                                          \
     908                 :            :                 });                                                             \
     909                 :            :             } while (0)
     910                 :            : 
     911                 :         24 :             int idx_base = rr->osd_vertices.num;
     912   [ +  +  -  +  :         24 :             EMIT_VERT(x0, y0); // idx 0: top left
                   -  + ]
     913   [ -  +  -  +  :         24 :             EMIT_VERT(x1, y0); // idx 1: top right
                   -  + ]
     914   [ -  +  -  +  :         24 :             EMIT_VERT(x0, y1); // idx 2: bottom left
                   -  + ]
     915   [ -  +  -  +  :         24 :             EMIT_VERT(x1, y1); // idx 3: bottom right
                   -  + ]
     916   [ +  +  -  +  :         24 :             PL_ARRAY_APPEND(rr, rr->osd_indices, idx_base + 0);
                   -  + ]
     917   [ -  +  -  +  :         24 :             PL_ARRAY_APPEND(rr, rr->osd_indices, idx_base + 1);
                   -  + ]
     918   [ -  +  -  +  :         24 :             PL_ARRAY_APPEND(rr, rr->osd_indices, idx_base + 2);
                   -  + ]
     919   [ -  +  -  +  :         24 :             PL_ARRAY_APPEND(rr, rr->osd_indices, idx_base + 2);
                   -  + ]
     920   [ -  +  +  +  :         24 :             PL_ARRAY_APPEND(rr, rr->osd_indices, idx_base + 1);
                   -  + ]
     921   [ -  +  -  +  :         24 :             PL_ARRAY_APPEND(rr, rr->osd_indices, idx_base + 3);
                   -  + ]
     922                 :            :         }
     923                 :            : 
     924                 :            :         // Draw parts
     925                 :         16 :         pl_shader sh = pl_dispatch_begin(rr->dp);
     926                 :         16 :         ident_t tex = sh_desc(sh, (struct pl_shader_desc) {
     927                 :            :             .desc = {
     928                 :            :                 .name = "osd_tex",
     929                 :            :                 .type = PL_DESC_SAMPLED_TEX,
     930                 :            :             },
     931                 :            :             .binding = {
     932                 :         16 :                 .object = ol.tex,
     933                 :         16 :                 .sample_mode = (ol.tex->params.format->caps & PL_FMT_CAP_LINEAR)
     934                 :            :                     ? PL_TEX_SAMPLE_LINEAR
     935                 :         16 :                     : PL_TEX_SAMPLE_NEAREST,
     936                 :            :             },
     937                 :            :         });
     938                 :            : 
     939                 :         16 :         sh_describe(sh, "overlay");
     940                 :         16 :         GLSL("// overlay \n");
     941                 :            : 
     942   [ +  +  -  - ]:         16 :         switch (ol.mode) {
     943                 :            :         case PL_OVERLAY_NORMAL:
     944                 :          8 :             GLSL("vec4 color = textureLod("$", coord, 0.0); \n", tex);
     945                 :          8 :             break;
     946                 :            :         case PL_OVERLAY_MONOCHROME:
     947                 :          8 :             GLSL("vec4 color = osd_color; \n");
     948                 :          8 :             break;
     949                 :            :         case PL_OVERLAY_MODE_COUNT:
     950                 :          0 :             pl_unreachable();
     951                 :            :         };
     952                 :            : 
     953                 :            :         static const struct pl_color_map_params osd_params = {
     954                 :            :             PL_COLOR_MAP_DEFAULTS
     955                 :            :             .tone_mapping_function = &pl_tone_map_linear,
     956                 :            :             .gamut_mapping         = &pl_gamut_map_saturation,
     957                 :            :         };
     958                 :            : 
     959                 :         16 :         sh->output = PL_SHADER_SIG_COLOR;
     960                 :         16 :         pl_shader_decode_color(sh, &ol.repr, NULL);
     961         [ -  + ]:         16 :         if (target->icc)
     962                 :            :             color.transfer = PL_COLOR_TRC_LINEAR;
     963                 :         16 :         pl_shader_color_map_ex(sh, &osd_params, pl_color_map_args(ol.color, color));
     964         [ -  + ]:         16 :         if (target->icc)
     965                 :          0 :             pl_icc_encode(sh, target->icc, &rr->icc_state[ICC_TARGET]);
     966                 :            : 
     967                 :         16 :         bool premul = repr.alpha == PL_ALPHA_PREMULTIPLIED;
     968                 :         16 :         pl_shader_encode_color(sh, &repr);
     969         [ +  + ]:         16 :         if (ol.mode == PL_OVERLAY_MONOCHROME) {
     970         [ +  - ]:         16 :             GLSL("color.%s *= textureLod("$", coord, 0.0).r; \n",
     971                 :            :                  premul ? "rgba" : "a", tex);
     972                 :            :         }
     973                 :            : 
     974                 :         16 :         swizzle_color(sh, comps, comp_map, true);
     975                 :            : 
     976                 :         16 :         struct pl_blend_params blend_params = {
     977         [ +  - ]:         16 :             .src_rgb = premul ? PL_BLEND_ONE : PL_BLEND_SRC_ALPHA,
     978                 :            :             .src_alpha = PL_BLEND_ONE,
     979                 :            :             .dst_rgb = PL_BLEND_ONE_MINUS_SRC_ALPHA,
     980                 :            :             .dst_alpha = PL_BLEND_ONE_MINUS_SRC_ALPHA,
     981                 :            :         };
     982                 :            : 
     983   [ -  +  +  + ]:         24 :         bool ok = pl_dispatch_vertex(rr->dp, pl_dispatch_vertex_params(
     984                 :            :             .shader = &sh,
     985                 :            :             .target = fbo,
     986                 :            :             .blend_params = (rr->errors & PL_RENDER_ERR_BLENDING)
     987                 :            :                             ? NULL : &blend_params,
     988                 :            :             .vertex_stride = sizeof(struct osd_vertex),
     989                 :            :             .num_vertex_attribs = ol.mode == PL_OVERLAY_NORMAL ? 2 : 3,
     990                 :            :             .vertex_attribs = rr->osd_attribs,
     991                 :            :             .vertex_position_idx = 0,
     992                 :            :             .vertex_coords = PL_COORDS_NORMALIZED,
     993                 :            :             .vertex_type = PL_PRIM_TRIANGLE_LIST,
     994                 :            :             .vertex_count = rr->osd_indices.num,
     995                 :            :             .vertex_data = rr->osd_vertices.elem,
     996                 :            :             .index_data = rr->osd_indices.elem,
     997                 :            :         ));
     998                 :            : 
     999         [ -  + ]:         16 :         if (!ok) {
    1000                 :          0 :             PL_ERR(rr, "Failed rendering overlays!");
    1001                 :          0 :             rr->errors |= PL_RENDER_ERR_OVERLAY;
    1002                 :          0 :             return;
    1003                 :            :         }
    1004                 :            :     }
    1005                 :            : }
    1006                 :            : 
    1007                 :         18 : static pl_tex get_hook_tex(void *priv, int width, int height)
    1008                 :            : {
    1009                 :            :     struct pass_state *pass = priv;
    1010                 :            : 
    1011                 :         18 :     return get_fbo(pass, width, height, NULL, 4, PL_DEBUG_TAG);
    1012                 :            : }
    1013                 :            : 
    1014                 :            : // Returns if any hook was applied (even if there were errors)
    1015                 :       6841 : static bool pass_hook(struct pass_state *pass, struct img *img,
    1016                 :            :                       enum pl_hook_stage stage)
    1017                 :            : {
    1018                 :       6841 :     const struct pl_render_params *params = pass->params;
    1019                 :       6841 :     pl_renderer rr = pass->rr;
    1020   [ +  +  +  + ]:       6841 :     if (!pass->fbofmt[4] || !stage)
    1021                 :            :         return false;
    1022                 :            : 
    1023                 :            :     bool ret = false;
    1024                 :            : 
    1025         [ +  + ]:       6124 :     for (int n = 0; n < params->num_hooks; n++) {
    1026                 :        100 :         const struct pl_hook *hook = params->hooks[n];
    1027         [ +  + ]:        100 :         if (!(hook->stages & stage))
    1028                 :        100 :             continue;
    1029                 :            : 
    1030                 :            :         // Hopefully the list of disabled hooks is small, search linearly.
    1031         [ -  + ]:         19 :         for (int i = 0; i < rr->disabled_hooks.num; i++) {
    1032         [ #  # ]:          0 :             if (rr->disabled_hooks.elem[i] != hook->signature)
    1033                 :            :                 continue;
    1034                 :          0 :             PL_TRACE(rr, "Skipping hook %d (0x%"PRIx64") stage 0x%x",
    1035                 :            :                      n, hook->signature, stage);
    1036                 :          0 :             goto hook_skip;
    1037                 :            :         }
    1038                 :            : 
    1039                 :         19 :         PL_TRACE(rr, "Dispatching hook %d (0x%"PRIx64") stage 0x%x",
    1040                 :            :                  n, hook->signature, stage);
    1041                 :         19 :         struct pl_hook_params hparams = {
    1042                 :         19 :             .gpu = rr->gpu,
    1043                 :         19 :             .dispatch = rr->dp,
    1044                 :            :             .get_tex = get_hook_tex,
    1045                 :            :             .priv = pass,
    1046                 :            :             .stage = stage,
    1047                 :            :             .rect = img->rect,
    1048                 :            :             .repr = img->repr,
    1049                 :            :             .color = img->color,
    1050                 :         19 :             .orig_repr = &pass->image.repr,
    1051                 :         19 :             .orig_color = &pass->image.color,
    1052                 :         19 :             .components = img->comps,
    1053                 :            :             .src_rect = pass->ref_rect,
    1054                 :            :             .dst_rect = pass->dst_rect,
    1055                 :            :         };
    1056                 :            : 
    1057                 :            :         // TODO: Add some sort of `test` API function to the hooks that allows
    1058                 :            :         // us to skip having to touch the `img` state at all for no-ops
    1059                 :            : 
    1060   [ +  -  -  + ]:         19 :         switch (hook->input) {
    1061                 :            :         case PL_HOOK_SIG_NONE:
    1062                 :            :             break;
    1063                 :            : 
    1064                 :         14 :         case PL_HOOK_SIG_TEX: {
    1065                 :         14 :             hparams.tex = img_tex(pass, img);
    1066         [ -  + ]:         14 :             if (!hparams.tex) {
    1067                 :          0 :                 PL_ERR(rr, "Failed dispatching shader prior to hook!");
    1068                 :          0 :                 goto hook_error;
    1069                 :            :             }
    1070                 :            :             break;
    1071                 :            :         }
    1072                 :            : 
    1073                 :          0 :         case PL_HOOK_SIG_COLOR:
    1074                 :          0 :             hparams.sh = img_sh(pass, img);
    1075                 :          0 :             break;
    1076                 :            : 
    1077                 :            :         case PL_HOOK_SIG_COUNT:
    1078                 :          0 :             pl_unreachable();
    1079                 :            :         }
    1080                 :            : 
    1081                 :         19 :         struct pl_hook_res res = hook->hook(hook->priv, &hparams);
    1082         [ -  + ]:         19 :         if (res.failed) {
    1083                 :          0 :             PL_ERR(rr, "Failed executing hook, disabling");
    1084                 :          0 :             goto hook_error;
    1085                 :            :         }
    1086                 :            : 
    1087                 :         19 :         bool resizable = pl_hook_stage_resizable(stage);
    1088   [ +  +  -  - ]:         19 :         switch (res.output) {
    1089                 :            :         case PL_HOOK_SIG_NONE:
    1090                 :            :             break;
    1091                 :            : 
    1092                 :         10 :         case PL_HOOK_SIG_TEX:
    1093         [ -  + ]:         10 :             if (!resizable) {
    1094         [ #  # ]:          0 :                 if (res.tex->params.w != img->w ||
    1095                 :          0 :                     res.tex->params.h != img->h ||
    1096   [ #  #  #  #  :          0 :                     !pl_rect2d_eq(res.rect, img->rect))
             #  #  #  # ]
    1097                 :            :                 {
    1098                 :          0 :                     PL_ERR(rr, "User hook tried resizing non-resizable stage!");
    1099                 :          0 :                     goto hook_error;
    1100                 :            :                 }
    1101                 :            :             }
    1102                 :            : 
    1103                 :         10 :             *img = (struct img) {
    1104                 :            :                 .tex    = res.tex,
    1105                 :            :                 .repr   = res.repr,
    1106                 :            :                 .color  = res.color,
    1107                 :            :                 .comps  = res.components,
    1108                 :            :                 .rect   = res.rect,
    1109                 :         10 :                 .w      = res.tex->params.w,
    1110                 :         10 :                 .h      = res.tex->params.h,
    1111                 :         10 :                 .unique = img->unique,
    1112                 :            :             };
    1113                 :         10 :             break;
    1114                 :            : 
    1115                 :          0 :         case PL_HOOK_SIG_COLOR:
    1116         [ #  # ]:          0 :             if (!resizable) {
    1117         [ #  # ]:          0 :                 if (res.sh->output_w != img->w ||
    1118         [ #  # ]:          0 :                     res.sh->output_h != img->h ||
    1119   [ #  #  #  #  :          0 :                     !pl_rect2d_eq(res.rect, img->rect))
             #  #  #  # ]
    1120                 :            :                 {
    1121                 :          0 :                     PL_ERR(rr, "User hook tried resizing non-resizable stage!");
    1122                 :          0 :                     goto hook_error;
    1123                 :            :                 }
    1124                 :            :             }
    1125                 :            : 
    1126                 :          0 :             *img = (struct img) {
    1127                 :            :                 .sh       = res.sh,
    1128                 :            :                 .repr     = res.repr,
    1129                 :            :                 .color    = res.color,
    1130                 :            :                 .comps    = res.components,
    1131                 :            :                 .rect     = res.rect,
    1132                 :          0 :                 .w        = res.sh->output_w,
    1133                 :          0 :                 .h        = res.sh->output_h,
    1134                 :          0 :                 .unique   = img->unique,
    1135                 :            :                 .err_enum = PL_RENDER_ERR_HOOKS,
    1136                 :            :                 .err_msg  = "Failed applying user hook",
    1137                 :          0 :                 .err_tex  = hparams.tex, // if any
    1138                 :            :             };
    1139                 :          0 :             break;
    1140                 :            : 
    1141                 :            :         case PL_HOOK_SIG_COUNT:
    1142                 :          0 :             pl_unreachable();
    1143                 :            :         }
    1144                 :            : 
    1145                 :            :         // a hook was performed successfully
    1146                 :            :         ret = true;
    1147                 :            : 
    1148                 :         19 : hook_skip:
    1149                 :         19 :         continue;
    1150                 :          0 : hook_error:
    1151   [ #  #  #  #  :          0 :         PL_ARRAY_APPEND(rr, rr->disabled_hooks, hook->signature);
                   #  # ]
    1152                 :          0 :         rr->errors |= PL_RENDER_ERR_HOOKS;
    1153                 :            :     }
    1154                 :            : 
    1155                 :            :     // Make sure the state remains as valid as possible, even if the resulting
    1156                 :            :     // shaders might end up nonsensical, to prevent segfaults
    1157   [ +  +  +  - ]:       6024 :     if (!img->tex && !img->sh)
    1158                 :          0 :         img->sh = pl_dispatch_begin(rr->dp);
    1159                 :            :     return ret;
    1160                 :            : }
    1161                 :            : 
    1162                 :        793 : static void hdr_update_peak(struct pass_state *pass)
    1163                 :            : {
    1164                 :        793 :     const struct pl_render_params *params = pass->params;
    1165                 :        793 :     pl_renderer rr = pass->rr;
    1166   [ +  +  +  + ]:        793 :     if (!params->peak_detect_params || !pl_color_space_is_hdr(&pass->img.color))
    1167                 :        771 :         goto cleanup;
    1168                 :            : 
    1169         [ -  + ]:         22 :     if (rr->errors & PL_RENDER_ERR_PEAK_DETECT)
    1170                 :          0 :         goto cleanup;
    1171                 :            : 
    1172   [ +  -  +  + ]:         22 :     if (pass->fbofmt[4] && !(pass->fbofmt[4]->caps & PL_FMT_CAP_STORABLE))
    1173                 :         10 :         goto cleanup;
    1174                 :            : 
    1175         [ -  + ]:         12 :     if (!rr->gpu->limits.max_ssbo_size)
    1176                 :          0 :         goto cleanup;
    1177                 :            : 
    1178                 :         12 :     float max_peak = pl_color_transfer_nominal_peak(pass->img.color.transfer) *
    1179                 :            :                      PL_COLOR_SDR_WHITE;
    1180         [ -  + ]:         12 :     if (pass->img.color.transfer == PL_COLOR_TRC_HLG)
    1181                 :          0 :         max_peak = pass->img.color.hdr.max_luma;
    1182         [ -  + ]:         12 :     if (max_peak <= pass->target.color.hdr.max_luma + 1e-6)
    1183                 :          0 :         goto cleanup; // no adaptation needed
    1184                 :            : 
    1185         [ -  + ]:         12 :     if (pass->img.color.hdr.avg_pq_y)
    1186                 :          0 :         goto cleanup; // DV metadata already present
    1187                 :            : 
    1188                 :            :     enum pl_hdr_metadata_type metadata = PL_HDR_METADATA_ANY;
    1189         [ +  - ]:         12 :     if (params->color_map_params)
    1190                 :         12 :         metadata = params->color_map_params->metadata;
    1191                 :            : 
    1192         [ -  + ]:         12 :     if (metadata && metadata != PL_HDR_METADATA_CIE_Y)
    1193                 :          0 :         goto cleanup; // metadata will be unused
    1194                 :            : 
    1195                 :            :     const struct pl_color_map_params *cpars = params->color_map_params;
    1196   [ +  -  -  + ]:         12 :     bool uses_ootf = cpars && cpars->tone_mapping_function == &pl_tone_map_st2094_40;
    1197         [ #  # ]:          0 :     if (uses_ootf && pass->img.color.hdr.ootf.num_anchors)
    1198                 :          0 :         goto cleanup; // HDR10+ OOTF is being used
    1199                 :            : 
    1200   [ -  +  -  - ]:         12 :     if (params->lut && params->lut_type == PL_LUT_CONVERSION)
    1201                 :          0 :         goto cleanup; // LUT handles tone mapping
    1202                 :            : 
    1203   [ -  +  -  - ]:         12 :     if (!pass->fbofmt[4] && !params->peak_detect_params->allow_delayed) {
    1204                 :          0 :         PL_WARN(rr, "Disabling peak detection because "
    1205                 :            :                 "`pl_peak_detect_params.allow_delayed` is false, but lack of "
    1206                 :            :                 "FBOs forces the result to be delayed.");
    1207                 :          0 :         rr->errors |= PL_RENDER_ERR_PEAK_DETECT;
    1208                 :          0 :         goto cleanup;
    1209                 :            :     }
    1210                 :            : 
    1211                 :         12 :     bool ok = pl_shader_detect_peak(img_sh(pass, &pass->img), pass->img.color,
    1212                 :         12 :                                     &rr->tone_map_state, params->peak_detect_params);
    1213         [ -  + ]:         12 :     if (!ok) {
    1214                 :          0 :         PL_WARN(rr, "Failed creating HDR peak detection shader.. disabling");
    1215                 :          0 :         rr->errors |= PL_RENDER_ERR_PEAK_DETECT;
    1216                 :          0 :         goto cleanup;
    1217                 :            :     }
    1218                 :            : 
    1219                 :         12 :     pass->need_peak_fbo = !params->peak_detect_params->allow_delayed;
    1220                 :         12 :     return;
    1221                 :            : 
    1222                 :        781 : cleanup:
    1223                 :            :     // No peak detection required or supported, so clean up the state to avoid
    1224                 :            :     // confusing it with later frames where peak detection is enabled again
    1225                 :        781 :     pl_reset_detected_peak(rr->tone_map_state);
    1226                 :            : }
    1227                 :            : 
    1228                 :          0 : bool pl_renderer_get_hdr_metadata(pl_renderer rr,
    1229                 :            :                                   struct pl_hdr_metadata *metadata)
    1230                 :            : {
    1231                 :          0 :     return pl_get_detected_hdr_metadata(rr->tone_map_state, metadata);
    1232                 :            : }
    1233                 :            : 
    1234                 :            : struct plane_state {
    1235                 :            :     enum plane_type type;
    1236                 :            :     struct pl_plane plane;
    1237                 :            :     struct img img; // for per-plane shaders
    1238                 :            :     pl_fmt fmt; // per-plane format after merge
    1239                 :            :     float plane_w, plane_h; // logical plane dimensions
    1240                 :            : };
    1241                 :            : 
    1242                 :            : static const char *plane_type_names[] = {
    1243                 :            :     [PLANE_INVALID] = "invalid",
    1244                 :            :     [PLANE_ALPHA]   = "alpha",
    1245                 :            :     [PLANE_CHROMA]  = "chroma",
    1246                 :            :     [PLANE_LUMA]    = "luma",
    1247                 :            :     [PLANE_RGB]     = "rgb",
    1248                 :            :     [PLANE_XYZ]     = "xyz",
    1249                 :            : };
    1250                 :            : 
    1251                 :        829 : static void log_plane_info(pl_renderer rr, const struct plane_state *st)
    1252                 :            : {
    1253                 :            :     const struct pl_plane *plane = &st->plane;
    1254                 :        829 :     PL_TRACE(rr, "    Type: %s", plane_type_names[st->type]);
    1255                 :            : 
    1256   [ -  +  +  -  :        829 :     switch (plane->components) {
                   -  - ]
    1257                 :          0 :     case 0:
    1258                 :          0 :         PL_TRACE(rr, "    Components: (none)");
    1259                 :          0 :         break;
    1260                 :        819 :     case 1:
    1261                 :        819 :         PL_TRACE(rr, "    Components: {%d}",
    1262                 :            :                  plane->component_mapping[0]);
    1263                 :        819 :         break;
    1264                 :         10 :     case 2:
    1265                 :         10 :         PL_TRACE(rr, "    Components: {%d %d}",
    1266                 :            :                  plane->component_mapping[0],
    1267                 :            :                  plane->component_mapping[1]);
    1268                 :         10 :         break;
    1269                 :          0 :     case 3:
    1270                 :          0 :         PL_TRACE(rr, "    Components: {%d %d %d}",
    1271                 :            :                  plane->component_mapping[0],
    1272                 :            :                  plane->component_mapping[1],
    1273                 :            :                  plane->component_mapping[2]);
    1274                 :          0 :         break;
    1275                 :          0 :     case 4:
    1276                 :          0 :         PL_TRACE(rr, "    Components: {%d %d %d %d}",
    1277                 :            :                  plane->component_mapping[0],
    1278                 :            :                  plane->component_mapping[1],
    1279                 :            :                  plane->component_mapping[2],
    1280                 :            :                  plane->component_mapping[3]);
    1281                 :          0 :         break;
    1282                 :            :     }
    1283                 :            : 
    1284                 :        829 :     PL_TRACE(rr, "    Rect: {%f %f} -> {%f %f}",
    1285                 :            :              st->img.rect.x0, st->img.rect.y0, st->img.rect.x1, st->img.rect.y1);
    1286                 :            : 
    1287                 :        829 :     PL_TRACE(rr, "    Bits: %d (used) / %d (sampled), shift %d",
    1288                 :            :              st->img.repr.bits.color_depth,
    1289                 :            :              st->img.repr.bits.sample_depth,
    1290                 :            :              st->img.repr.bits.bit_shift);
    1291                 :        829 : }
    1292                 :            : 
    1293                 :            : // Returns true if debanding was applied
    1294                 :        802 : static bool plane_deband(struct pass_state *pass, struct img *img, float neutral[3])
    1295                 :            : {
    1296                 :        802 :     const struct pl_render_params *params = pass->params;
    1297                 :            :     const struct pl_frame *image = &pass->image;
    1298                 :        802 :     pl_renderer rr = pass->rr;
    1299         [ +  - ]:        802 :     if ((rr->errors & PL_RENDER_ERR_DEBANDING) ||
    1300   [ +  +  +  - ]:        802 :         !params->deband_params || !pass->fbofmt[4])
    1301                 :            :     {
    1302                 :            :         return false;
    1303                 :            :     }
    1304                 :            : 
    1305                 :         16 :     struct pl_color_repr repr = img->repr;
    1306                 :         48 :     struct pl_sample_src src = {
    1307                 :         16 :         .tex = img_tex(pass, img),
    1308                 :         16 :         .components = img->comps,
    1309                 :         16 :         .scale = pl_color_repr_normalize(&repr),
    1310                 :            :     };
    1311                 :            : 
    1312                 :            :     // Divide the deband grain scale by the effective current colorspace nominal
    1313                 :            :     // peak, to make sure the output intensity of the grain is as independent
    1314                 :            :     // of the source as possible, even though it happens this early in the
    1315                 :            :     // process (well before any linearization / output adaptation)
    1316                 :         16 :     struct pl_deband_params dparams = *params->deband_params;
    1317                 :         16 :     dparams.grain /= image->color.hdr.max_luma / PL_COLOR_SDR_WHITE;
    1318                 :            :     memcpy(dparams.grain_neutral, neutral, sizeof(dparams.grain_neutral));
    1319                 :            : 
    1320                 :         16 :     img->tex = NULL;
    1321                 :         16 :     img->sh = pl_dispatch_begin_ex(rr->dp, true);
    1322                 :         16 :     pl_shader_deband(img->sh, &src, &dparams);
    1323                 :         16 :     img->err_msg = "Failed applying debanding... disabling!";
    1324                 :         16 :     img->err_enum = PL_RENDER_ERR_DEBANDING;
    1325                 :         16 :     img->err_tex = src.tex;
    1326                 :         16 :     img->repr = repr;
    1327                 :         16 :     return true;
    1328                 :            : }
    1329                 :            : 
    1330                 :            : // Returns true if grain was applied
    1331                 :        802 : static bool plane_film_grain(struct pass_state *pass, int plane_idx,
    1332                 :            :                              struct plane_state *st,
    1333                 :            :                              const struct plane_state *ref)
    1334                 :            : {
    1335                 :            :     const struct pl_frame *image = &pass->image;
    1336                 :        802 :     pl_renderer rr = pass->rr;
    1337         [ -  + ]:        802 :     if (rr->errors & PL_RENDER_ERR_FILM_GRAIN)
    1338                 :            :         return false;
    1339                 :            : 
    1340                 :        802 :     struct img *img = &st->img;
    1341                 :            :     struct pl_plane *plane = &st->plane;
    1342                 :        802 :     struct pl_color_repr repr = image->repr;
    1343                 :        802 :     bool is_orig_repr = pl_color_repr_equal(&st->img.repr, &image->repr);
    1344         [ -  + ]:        802 :     if (!is_orig_repr) {
    1345                 :            :         // Propagate the original color depth to the film grain algorithm, but
    1346                 :            :         // update the sample depth and effective bit shift based on the state
    1347                 :            :         // of the current texture, which is guaranteed to already be
    1348                 :            :         // normalized.
    1349         [ #  # ]:          0 :         pl_assert(st->img.repr.bits.bit_shift == 0);
    1350                 :          0 :         repr.bits.sample_depth = st->img.repr.bits.sample_depth;
    1351                 :          0 :         repr.bits.bit_shift = repr.bits.sample_depth - repr.bits.color_depth;
    1352                 :            :     }
    1353                 :            : 
    1354                 :        802 :     struct pl_film_grain_params grain_params = {
    1355                 :            :         .data = image->film_grain,
    1356                 :        802 :         .luma_tex = ref->plane.texture,
    1357                 :            :         .repr = &repr,
    1358                 :        802 :         .components = plane->components,
    1359                 :            :     };
    1360                 :            : 
    1361   [ +  +  +  - ]:        802 :     switch (image->film_grain.type) {
    1362                 :            :     case PL_FILM_GRAIN_NONE: return false;
    1363                 :            :     case PL_FILM_GRAIN_H274: break;
    1364                 :            :     case PL_FILM_GRAIN_AV1:
    1365                 :            :         grain_params.luma_tex = ref->plane.texture;
    1366         [ +  + ]:          8 :         for (int c = 0; c < ref->plane.components; c++) {
    1367         [ +  - ]:          4 :             if (ref->plane.component_mapping[c] == PL_CHANNEL_Y)
    1368                 :          4 :                 grain_params.luma_comp = c;
    1369                 :            :         }
    1370                 :            :         break;
    1371                 :          0 :     default: pl_unreachable();
    1372                 :            :     }
    1373                 :            : 
    1374         [ +  + ]:         16 :     for (int c = 0; c < plane->components; c++)
    1375                 :          8 :         grain_params.component_mapping[c] = plane->component_mapping[c];
    1376                 :            : 
    1377         [ -  + ]:          8 :     if (!pl_needs_film_grain(&grain_params))
    1378                 :            :         return false;
    1379                 :            : 
    1380         [ -  + ]:          8 :     if (!pass->fbofmt[plane->components]) {
    1381                 :          0 :         PL_ERR(rr, "Film grain required but no renderable format available.. "
    1382                 :            :               "disabling!");
    1383                 :          0 :         rr->errors |= PL_RENDER_ERR_FILM_GRAIN;
    1384                 :          0 :         return false;
    1385                 :            :     }
    1386                 :            : 
    1387                 :          8 :     grain_params.tex = img_tex(pass, img);
    1388         [ -  + ]:          8 :     if (!grain_params.tex)
    1389                 :            :         return false;
    1390                 :            : 
    1391                 :          8 :     img->sh = pl_dispatch_begin_ex(rr->dp, true);
    1392         [ +  + ]:          8 :     if (!pl_shader_film_grain(img->sh, &rr->grain_state[plane_idx], &grain_params)) {
    1393                 :          2 :         pl_dispatch_abort(rr->dp, &img->sh);
    1394                 :          2 :         rr->errors |= PL_RENDER_ERR_FILM_GRAIN;
    1395                 :          2 :         return false;
    1396                 :            :     }
    1397                 :            : 
    1398                 :          6 :     img->tex = NULL;
    1399                 :          6 :     img->err_msg = "Failed applying film grain.. disabling!";
    1400                 :          6 :     img->err_enum = PL_RENDER_ERR_FILM_GRAIN;
    1401                 :          6 :     img->err_tex = grain_params.tex;
    1402         [ +  - ]:          6 :     if (is_orig_repr)
    1403                 :          6 :         img->repr = repr;
    1404                 :            :     return true;
    1405                 :            : }
    1406                 :            : 
    1407                 :            : static const enum pl_hook_stage plane_hook_stages[] = {
    1408                 :            :     [PLANE_ALPHA]   = PL_HOOK_ALPHA_INPUT,
    1409                 :            :     [PLANE_CHROMA]  = PL_HOOK_CHROMA_INPUT,
    1410                 :            :     [PLANE_LUMA]    = PL_HOOK_LUMA_INPUT,
    1411                 :            :     [PLANE_RGB]     = PL_HOOK_RGB_INPUT,
    1412                 :            :     [PLANE_XYZ]     = PL_HOOK_XYZ_INPUT,
    1413                 :            : };
    1414                 :            : 
    1415                 :            : static const enum pl_hook_stage plane_scaled_hook_stages[] = {
    1416                 :            :     [PLANE_ALPHA]   = PL_HOOK_ALPHA_SCALED,
    1417                 :            :     [PLANE_CHROMA]  = PL_HOOK_CHROMA_SCALED,
    1418                 :            :     [PLANE_LUMA]    = 0, // never hooked
    1419                 :            :     [PLANE_RGB]     = 0,
    1420                 :            :     [PLANE_XYZ]     = 0,
    1421                 :            : };
    1422                 :            : 
    1423                 :            : static enum pl_lut_type guess_frame_lut_type(const struct pl_frame *frame,
    1424                 :            :                                              bool reversed)
    1425                 :            : {
    1426   [ +  +  +  +  :       2671 :     if (!frame->lut)
                   +  + ]
    1427                 :            :         return PL_LUT_UNKNOWN;
    1428   [ +  +  +  +  :         96 :     if (frame->lut_type)
                   +  + ]
    1429                 :            :         return frame->lut_type;
    1430                 :            : 
    1431                 :         24 :     enum pl_color_system sys_in = frame->lut->repr_in.sys;
    1432                 :         24 :     enum pl_color_system sys_out = frame->lut->repr_out.sys;
    1433                 :            :     if (reversed)
    1434                 :            :         PL_SWAP(sys_in, sys_out);
    1435                 :            : 
    1436   [ +  -  +  -  :         24 :     if (sys_in == PL_COLOR_SYSTEM_RGB && sys_out == sys_in)
                   +  - ]
    1437                 :            :         return PL_LUT_NORMALIZED;
    1438                 :            : 
    1439   [ -  +  -  -  :         24 :     if (sys_in == frame->repr.sys && sys_out == PL_COLOR_SYSTEM_RGB)
          +  -  -  -  +  
                -  -  - ]
    1440                 :            :         return PL_LUT_CONVERSION;
    1441                 :            : 
    1442                 :            :     // Unknown, just fall back to the default
    1443                 :            :     return PL_LUT_NATIVE;
    1444                 :            : }
    1445                 :            : 
    1446                 :          5 : static pl_fmt merge_fmt(struct pass_state *pass, const struct img *a,
    1447                 :            :                         const struct img *b)
    1448                 :            : {
    1449                 :          5 :     pl_renderer rr = pass->rr;
    1450   [ +  -  -  - ]:          5 :     pl_fmt fmta = a->tex ? a->tex->params.format : PL_DEF(a->fmt, pass->fbofmt[a->comps]);
    1451   [ +  -  -  - ]:          5 :     pl_fmt fmtb = b->tex ? b->tex->params.format : PL_DEF(b->fmt, pass->fbofmt[b->comps]);
    1452         [ -  + ]:          5 :     pl_assert(fmta && fmtb);
    1453         [ +  - ]:          5 :     if (fmta->type != fmtb->type)
    1454                 :            :         return NULL;
    1455                 :            : 
    1456                 :          5 :     int num_comps = PL_MIN(4, a->comps + b->comps);
    1457                 :          5 :     int min_depth = PL_MAX(a->repr.bits.sample_depth, b->repr.bits.sample_depth);
    1458                 :            : 
    1459                 :            :     // Only return formats that support all relevant caps of both formats
    1460                 :            :     const enum pl_fmt_caps mask = PL_FMT_CAP_SAMPLEABLE | PL_FMT_CAP_LINEAR;
    1461                 :          5 :     enum pl_fmt_caps req_caps = (fmta->caps & mask) | (fmtb->caps & mask);
    1462                 :            : 
    1463                 :          5 :     return pl_find_fmt(rr->gpu, fmta->type, num_comps, min_depth, 0, req_caps);
    1464                 :            : }
    1465                 :            : 
    1466                 :            : // Applies a series of rough heuristics to figure out whether we expect any
    1467                 :            : // performance gains from plane merging. This is basically a series of checks
    1468                 :            : // for operations that we *know* benefit from merged planes
    1469                 :        802 : static bool want_merge(struct pass_state *pass,
    1470                 :            :                        const struct plane_state *st,
    1471                 :            :                        const struct plane_state *ref)
    1472                 :            : {
    1473                 :        802 :     const struct pl_render_params *params = pass->params;
    1474                 :        802 :     const pl_renderer rr = pass->rr;
    1475         [ +  + ]:        802 :     if (!pass->fbofmt[4])
    1476                 :            :         return false;
    1477                 :            : 
    1478                 :            :     // Debanding
    1479   [ +  -  +  + ]:        798 :     if (!(rr->errors & PL_RENDER_ERR_DEBANDING) && params->deband_params)
    1480                 :            :         return true;
    1481                 :            : 
    1482                 :            :     // Other plane hooks, which are generally nontrivial
    1483                 :        782 :     enum pl_hook_stage stage = plane_hook_stages[st->type];
    1484         [ +  + ]:        797 :     for (int i = 0; i < params->num_hooks; i++) {
    1485         [ +  + ]:         20 :         if (params->hooks[i]->stages & stage)
    1486                 :            :             return true;
    1487                 :            :     }
    1488                 :            : 
    1489                 :            :     // Non-trivial scaling
    1490                 :        777 :     struct pl_sample_src src = {
    1491                 :        777 :         .new_w = ref->img.w,
    1492                 :        777 :         .new_h = ref->img.h,
    1493                 :            :         .rect = {
    1494                 :        777 :             .x1 = st->img.w,
    1495                 :        777 :             .y1 = st->img.h,
    1496                 :            :         },
    1497                 :            :     };
    1498                 :            : 
    1499                 :        777 :     struct sampler_info info = sample_src_info(pass, &src, SAMPLER_PLANE);
    1500         [ -  + ]:        777 :     if (info.type == SAMPLER_COMPLEX)
    1501                 :            :         return true;
    1502                 :            : 
    1503                 :            :     // Film grain synthesis, can be merged for compatible channels, saving on
    1504                 :            :     // redundant sampling of the grain/offset textures
    1505                 :        777 :     struct pl_film_grain_params grain_params = {
    1506                 :            :         .data = pass->image.film_grain,
    1507                 :        777 :         .repr = (struct pl_color_repr *) &st->img.repr,
    1508                 :        777 :         .components = st->plane.components,
    1509                 :            :     };
    1510                 :            : 
    1511         [ +  + ]:       1554 :     for (int c = 0; c < st->plane.components; c++)
    1512                 :        777 :         grain_params.component_mapping[c] = st->plane.component_mapping[c];
    1513                 :            : 
    1514   [ -  +  +  + ]:       1554 :     if (!(rr->errors & PL_RENDER_ERR_FILM_GRAIN) &&
    1515                 :        777 :         pl_needs_film_grain(&grain_params))
    1516                 :            :     {
    1517                 :            :         return true;
    1518                 :            :     }
    1519                 :            : 
    1520                 :            :     return false;
    1521                 :            : }
    1522                 :            : 
    1523                 :            : // This scales and merges all of the source images, and initializes pass->img.
    1524                 :        797 : static bool pass_read_image(struct pass_state *pass)
    1525                 :            : {
    1526                 :        797 :     const struct pl_render_params *params = pass->params;
    1527                 :            :     struct pl_frame *image = &pass->image;
    1528                 :        797 :     pl_renderer rr = pass->rr;
    1529                 :            : 
    1530                 :            :     struct plane_state planes[4];
    1531                 :        797 :     struct plane_state *ref = &planes[pass->src_ref];
    1532   [ +  -  -  + ]:        797 :     pl_assert(pass->src_ref >= 0 && pass->src_ref < image->num_planes);
    1533                 :            : 
    1534         [ +  + ]:       1604 :     for (int i = 0; i < image->num_planes; i++) {
    1535                 :        807 :         planes[i] = (struct plane_state) {
    1536                 :        807 :             .type = detect_plane_type(&image->planes[i], &image->repr),
    1537                 :            :             .plane = image->planes[i],
    1538                 :            :             .img = {
    1539                 :        807 :                 .w = image->planes[i].texture->params.w,
    1540                 :        807 :                 .h = image->planes[i].texture->params.h,
    1541                 :            :                 .tex = image->planes[i].texture,
    1542                 :            :                 .repr = image->repr,
    1543                 :            :                 .color = image->color,
    1544                 :        807 :                 .comps = image->planes[i].components,
    1545                 :            :             },
    1546                 :        807 :             .fmt = image->planes[i].texture->params.format,
    1547                 :            :         };
    1548                 :            : 
    1549                 :            :         // Explicitly skip alpha channel when overridden
    1550         [ +  - ]:        807 :         if (image->repr.alpha == PL_ALPHA_NONE) {
    1551         [ -  + ]:        807 :             if (planes[i].type == PLANE_ALPHA) {
    1552                 :          0 :                 planes[i].type = PLANE_INVALID;
    1553                 :            :                 continue;
    1554                 :            :             } else {
    1555         [ +  + ]:       1614 :                 for (int j = 0; j < planes[i].plane.components; j++) {
    1556         [ -  + ]:        807 :                     if (planes[i].plane.component_mapping[j] == PL_CHANNEL_A)
    1557                 :          0 :                         planes[i].plane.component_mapping[j] = PL_CHANNEL_NONE;
    1558                 :            :                 }
    1559                 :            :             }
    1560                 :            :         }
    1561                 :            : 
    1562                 :            :         // Deinterlace plane if needed
    1563   [ +  +  -  + ]:        807 :         if (image->field != PL_FIELD_NONE && params->deinterlace_params &&
    1564   [ #  #  #  # ]:          0 :             pass->fbofmt[4] && !(rr->errors & PL_RENDER_ERR_DEINTERLACING))
    1565                 :            :         {
    1566                 :            :             struct img *img = &planes[i].img;
    1567                 :          0 :             struct pl_deinterlace_source src = {
    1568                 :            :                 .cur.top  = img->tex,
    1569         [ #  # ]:          0 :                 .prev.top = image->prev ? image->prev->planes[i].texture : NULL,
    1570         [ #  # ]:          0 :                 .next.top = image->next ? image->next->planes[i].texture : NULL,
    1571                 :            :                 .field    = image->field,
    1572                 :          0 :                 .first_field = image->first_field,
    1573                 :          0 :                 .component_mask = (1 << img->comps) - 1,
    1574                 :            :             };
    1575                 :            : 
    1576                 :          0 :             img->tex = NULL;
    1577                 :          0 :             img->sh = pl_dispatch_begin_ex(pass->rr->dp, true);
    1578                 :          0 :             pl_shader_deinterlace(img->sh, &src, params->deinterlace_params);
    1579                 :          0 :             img->err_msg = "Failed deinterlacing plane.. disabling!";
    1580                 :          0 :             img->err_enum = PL_RENDER_ERR_DEINTERLACING;
    1581                 :          0 :             img->err_tex = planes[i].plane.texture;
    1582                 :            :         }
    1583                 :            :     }
    1584                 :            : 
    1585                 :            :     // Original ref texture, even after preprocessing
    1586                 :        797 :     pl_tex ref_tex = ref->plane.texture;
    1587                 :            : 
    1588                 :            :     // Merge all compatible planes into 'combined' shaders
    1589         [ +  + ]:       1604 :     for (int i = 0; i < image->num_planes; i++) {
    1590                 :        807 :         struct plane_state *sti = &planes[i];
    1591         [ +  + ]:        807 :         if (!sti->type)
    1592                 :          5 :             continue;
    1593         [ +  + ]:        802 :         if (!want_merge(pass, sti, ref))
    1594                 :        773 :             continue;
    1595                 :            : 
    1596                 :            :         bool did_merge = false;
    1597         [ +  + ]:         34 :         for (int j = i+1; j < image->num_planes; j++) {
    1598                 :            :             struct plane_state *stj = &planes[j];
    1599                 :          5 :             bool merge = sti->type == stj->type &&
    1600         [ +  - ]:          5 :                          sti->img.w == stj->img.w &&
    1601                 :          5 :                          sti->img.h == stj->img.h &&
    1602   [ +  -  +  - ]:         10 :                          sti->plane.shift_x == stj->plane.shift_x &&
    1603         [ -  + ]:          5 :                          sti->plane.shift_y == stj->plane.shift_y;
    1604         [ -  + ]:          5 :             if (!merge)
    1605                 :          0 :                 continue;
    1606                 :            : 
    1607                 :          5 :             pl_fmt fmt = merge_fmt(pass, &sti->img, &stj->img);
    1608         [ -  + ]:          5 :             if (!fmt)
    1609                 :          0 :                 continue;
    1610                 :            : 
    1611                 :          5 :             PL_TRACE(rr, "Merging plane %d into plane %d", j, i);
    1612                 :          5 :             pl_shader sh = sti->img.sh;
    1613         [ +  - ]:          5 :             if (!sh) {
    1614                 :          5 :                 sh = sti->img.sh = pl_dispatch_begin_ex(pass->rr->dp, true);
    1615                 :          5 :                 pl_shader_sample_direct(sh, pl_sample_src( .tex = sti->img.tex ));
    1616                 :          5 :                 sti->img.tex = NULL;
    1617                 :            :             }
    1618                 :            : 
    1619                 :          5 :             pl_shader psh = NULL;
    1620         [ +  - ]:          5 :             if (!stj->img.sh) {
    1621                 :          5 :                 psh = pl_dispatch_begin_ex(pass->rr->dp, true);
    1622                 :          5 :                 pl_shader_sample_direct(psh, pl_sample_src( .tex = stj->img.tex ));
    1623                 :            :             }
    1624                 :            : 
    1625         [ -  + ]:          5 :             ident_t sub = sh_subpass(sh, psh ? psh : stj->img.sh);
    1626                 :          5 :             pl_dispatch_abort(rr->dp, &psh);
    1627         [ +  - ]:          5 :             if (!sub)
    1628                 :            :                 break; // skip merging
    1629                 :            : 
    1630                 :          5 :             sh_describe(sh, "merging planes");
    1631                 :          5 :             GLSL("{                 \n"
    1632                 :            :                  "vec4 tmp = "$"(); \n", sub);
    1633         [ +  + ]:         10 :             for (int jc = 0; jc < stj->img.comps; jc++) {
    1634                 :          5 :                 int map = stj->plane.component_mapping[jc];
    1635         [ -  + ]:          5 :                 if (map == PL_CHANNEL_NONE)
    1636                 :          0 :                     continue;
    1637                 :          5 :                 int ic = sti->img.comps++;
    1638         [ -  + ]:          5 :                 pl_assert(ic < 4);
    1639                 :          5 :                 GLSL("color[%d] = tmp[%d]; \n", ic, jc);
    1640                 :          5 :                 sti->plane.components = sti->img.comps;
    1641                 :          5 :                 sti->plane.component_mapping[ic] = map;
    1642                 :            :             }
    1643                 :          5 :             GLSL("} \n");
    1644                 :            : 
    1645                 :          5 :             sti->img.fmt = fmt;
    1646                 :          5 :             sti->fmt = fmt;
    1647                 :          5 :             pl_dispatch_abort(rr->dp, &stj->img.sh);
    1648                 :          5 :             *stj = (struct plane_state) {0};
    1649                 :            :             did_merge = true;
    1650                 :            :         }
    1651                 :            : 
    1652         [ +  + ]:         29 :         if (!did_merge)
    1653                 :         24 :             continue;
    1654                 :            : 
    1655         [ -  + ]:          5 :         if (!img_tex(pass, &sti->img)) {
    1656                 :          0 :             PL_ERR(rr, "Failed dispatching plane merging shader, disabling FBOs!");
    1657                 :          0 :             memset(pass->fbofmt, 0, sizeof(pass->fbofmt));
    1658                 :          0 :             rr->errors |= PL_RENDER_ERR_FBO;
    1659                 :          0 :             return false;
    1660                 :            :         }
    1661                 :            :     }
    1662                 :            : 
    1663                 :        797 :     int bits = image->repr.bits.sample_depth;
    1664         [ +  + ]:        797 :     float out_scale = bits ? (1llu << bits) / ((1llu << bits) - 1.0f) : 1.0f;
    1665                 :        797 :     float neutral_luma = 0.0, neutral_chroma = 0.5f * out_scale;
    1666         [ +  + ]:        797 :     if (pl_color_levels_guess(&image->repr) == PL_COLOR_LEVELS_LIMITED)
    1667                 :          5 :         neutral_luma = 16 / 256.0f * out_scale;
    1668         [ -  + ]:        797 :     if (!pl_color_system_is_ycbcr_like(image->repr.sys))
    1669                 :            :         neutral_chroma = neutral_luma;
    1670                 :            : 
    1671                 :            :     // Compute the sampling rc of each plane
    1672         [ +  + ]:       1604 :     for (int i = 0; i < image->num_planes; i++) {
    1673                 :        807 :         struct plane_state *st = &planes[i];
    1674         [ +  + ]:        807 :         if (!st->type)
    1675                 :          5 :             continue;
    1676                 :            : 
    1677                 :        802 :         float rx = (float) st->plane.texture->params.w / ref_tex->params.w,
    1678                 :        802 :               ry = (float) st->plane.texture->params.h / ref_tex->params.h;
    1679                 :            : 
    1680                 :            :         // Only accept integer scaling ratios. This accounts for the fact that
    1681                 :            :         // fractionally subsampled planes get rounded up to the nearest integer
    1682                 :            :         // size, which we want to discard.
    1683         [ +  + ]:        802 :         float rrx = rx >= 1 ? roundf(rx) : 1.0 / roundf(1.0 / rx),
    1684         [ +  + ]:        802 :               rry = ry >= 1 ? roundf(ry) : 1.0 / roundf(1.0 / ry);
    1685                 :            : 
    1686                 :        802 :         float sx = st->plane.shift_x,
    1687                 :        802 :               sy = st->plane.shift_y;
    1688                 :            : 
    1689                 :        802 :         st->img.rect = (pl_rect2df) {
    1690                 :        802 :             .x0 = (image->crop.x0 - sx) * rrx,
    1691                 :        802 :             .y0 = (image->crop.y0 - sy) * rry,
    1692                 :        802 :             .x1 = (image->crop.x1 - sx) * rrx,
    1693                 :        802 :             .y1 = (image->crop.y1 - sy) * rry,
    1694                 :            :         };
    1695                 :            : 
    1696                 :        802 :         st->plane_w = ref_tex->params.w * rrx;
    1697                 :        802 :         st->plane_h = ref_tex->params.h * rry;
    1698                 :            : 
    1699                 :        802 :         PL_TRACE(rr, "Plane %d:", i);
    1700                 :        802 :         log_plane_info(rr, st);
    1701                 :            : 
    1702                 :        802 :         float neutral[3] = {0.0};
    1703         [ +  + ]:       1609 :         for (int c = 0, idx = 0; c < st->plane.components; c++) {
    1704      [ +  +  - ]:        807 :             switch (st->plane.component_mapping[c]) {
    1705                 :        797 :             case PL_CHANNEL_Y: neutral[idx++] = neutral_luma; break;
    1706                 :         10 :             case PL_CHANNEL_U: // fall through
    1707                 :         10 :             case PL_CHANNEL_V: neutral[idx++] = neutral_chroma; break;
    1708                 :            :             }
    1709                 :            :         }
    1710                 :            : 
    1711                 :            :         // The order of operations (deband -> film grain -> user hooks) is
    1712                 :            :         // chosen to maximize quality. Note that film grain requires unmodified
    1713                 :            :         // plane sizes, so it has to be before user hooks. As for debanding,
    1714                 :            :         // it's reduced in quality after e.g. plane scalers as well. It's also
    1715                 :            :         // made less effective by performing film grain synthesis first.
    1716                 :            : 
    1717         [ +  + ]:        802 :         if (plane_deband(pass, &st->img, neutral)) {
    1718                 :         16 :             PL_TRACE(rr, "After debanding:");
    1719                 :         16 :             log_plane_info(rr, st);
    1720                 :            :         }
    1721                 :            : 
    1722         [ +  + ]:        802 :         if (plane_film_grain(pass, i, st, ref)) {
    1723                 :          6 :             PL_TRACE(rr, "After film grain:");
    1724                 :          6 :             log_plane_info(rr, st);
    1725                 :            :         }
    1726                 :            : 
    1727         [ +  + ]:        802 :         if (pass_hook(pass, &st->img, plane_hook_stages[st->type])) {
    1728                 :          5 :             st->plane.texture = st->img.tex;
    1729                 :          5 :             st->plane_w = st->img.w;
    1730                 :          5 :             st->plane_h = st->img.h;
    1731                 :          5 :             PL_TRACE(rr, "After user hooks:");
    1732                 :          5 :             log_plane_info(rr, st);
    1733                 :            :         }
    1734                 :            :     }
    1735                 :            : 
    1736                 :        797 :     pl_shader sh = pl_dispatch_begin_ex(rr->dp, true);
    1737                 :        797 :     sh_require(sh, PL_SHADER_SIG_NONE, 0, 0);
    1738                 :            : 
    1739                 :            :     // Initialize the color to black
    1740                 :        797 :     GLSL("vec4 color = vec4("$", vec2("$"), 1.0);   \n"
    1741                 :            :          "// pass_read_image                        \n"
    1742                 :            :          "{                                         \n"
    1743                 :            :          "vec4 tmp;                                 \n",
    1744                 :            :          SH_FLOAT(neutral_luma), SH_FLOAT(neutral_chroma));
    1745                 :            : 
    1746                 :            :     // For quality reasons, explicitly drop subpixel offsets from the ref rect
    1747                 :            :     // and re-add them as part of `pass->img.rect`, always rounding towards 0.
    1748                 :            :     // Additionally, drop anamorphic subpixel mismatches.
    1749                 :        797 :     const pl_rect2df ref_rc = ref->img.rect;
    1750                 :            :     pl_rect2d ref_rounded;
    1751                 :        797 :     ref_rounded.x0 = truncf(ref_rc.x0);
    1752                 :        797 :     ref_rounded.y0 = truncf(ref_rc.y0);
    1753                 :        797 :     ref_rounded.x1 = ref_rounded.x0 + roundf(pl_rect_w(ref_rc));
    1754                 :        797 :     ref_rounded.y1 = ref_rounded.y0 + roundf(pl_rect_h(ref_rc));
    1755                 :            : 
    1756                 :        797 :     PL_TRACE(rr, "Rounded reference rect: {%d %d %d %d}",
    1757                 :            :              ref_rounded.x0, ref_rounded.y0,
    1758                 :            :              ref_rounded.x1, ref_rounded.y1);
    1759                 :            : 
    1760                 :        797 :     float off_x = ref_rc.x0 - ref_rounded.x0,
    1761                 :        797 :           off_y = ref_rc.y0 - ref_rounded.y0,
    1762                 :        797 :           stretch_x = pl_rect_w(ref_rounded) / pl_rect_w(ref_rc),
    1763                 :        797 :           stretch_y = pl_rect_h(ref_rounded) / pl_rect_h(ref_rc);
    1764                 :            : 
    1765         [ +  + ]:       1604 :     for (int i = 0; i < image->num_planes; i++) {
    1766                 :            :         struct plane_state *st = &planes[i];
    1767                 :            :         const struct pl_plane *plane = &st->plane;
    1768         [ +  + ]:        807 :         if (!st->type)
    1769                 :          5 :             continue;
    1770                 :            : 
    1771                 :        802 :         float scale_x = pl_rect_w(st->img.rect) / pl_rect_w(ref_rc),
    1772                 :        802 :               scale_y = pl_rect_h(st->img.rect) / pl_rect_h(ref_rc),
    1773                 :        802 :               base_x = st->img.rect.x0 - scale_x * off_x,
    1774                 :        802 :               base_y = st->img.rect.y0 - scale_y * off_y;
    1775                 :            : 
    1776                 :       1604 :         struct pl_sample_src src = {
    1777                 :        802 :             .components = plane->components,
    1778                 :        802 :             .address_mode = plane->address_mode,
    1779                 :        802 :             .scale      = pl_color_repr_normalize(&st->img.repr),
    1780                 :            :             .new_w      = pl_rect_w(ref_rounded),
    1781                 :            :             .new_h      = pl_rect_h(ref_rounded),
    1782                 :            :             .rect = {
    1783                 :            :                 base_x,
    1784                 :            :                 base_y,
    1785                 :        802 :                 base_x + stretch_x * pl_rect_w(st->img.rect),
    1786                 :        802 :                 base_y + stretch_y * pl_rect_h(st->img.rect),
    1787                 :            :             },
    1788                 :            :         };
    1789                 :            : 
    1790         [ -  + ]:        802 :         if (plane->flipped) {
    1791                 :          0 :             src.rect.y0 = st->plane_h - src.rect.y0;
    1792                 :          0 :             src.rect.y1 = st->plane_h - src.rect.y1;
    1793                 :            :         }
    1794                 :            : 
    1795         [ +  - ]:       1604 :         PL_TRACE(rr, "Aligning plane %d: {%f %f %f %f} -> {%f %f %f %f}%s",
    1796                 :            :                  i, st->img.rect.x0, st->img.rect.y0,
    1797                 :            :                  st->img.rect.x1, st->img.rect.y1,
    1798                 :            :                  src.rect.x0, src.rect.y0,
    1799                 :            :                  src.rect.x1, src.rect.y1,
    1800                 :            :                  plane->flipped ? " (flipped) " : "");
    1801                 :            : 
    1802                 :        802 :         st->img.unique = true;
    1803                 :        802 :         pl_rect2d unscaled = { .x1 = src.new_w, .y1 = src.new_h };
    1804   [ +  +  +  - ]:        802 :         if (st->img.sh && st->img.w == src.new_w && st->img.h == src.new_h &&
    1805   [ +  -  +  -  :         22 :             pl_rect2d_eq(src.rect, unscaled))
             +  -  -  + ]
    1806                 :            :         {
    1807                 :            :             // Image rects are already equal, no indirect scaling needed
    1808                 :            :         } else {
    1809                 :        780 :             src.tex = img_tex(pass, &st->img);
    1810                 :        780 :             st->img.tex = NULL;
    1811                 :        780 :             st->img.sh = pl_dispatch_begin_ex(rr->dp, true);
    1812                 :        780 :             dispatch_sampler(pass, st->img.sh, &rr->samplers_src[i],
    1813                 :            :                              SAMPLER_PLANE, NULL, &src);
    1814                 :        780 :             st->img.err_enum |= PL_RENDER_ERR_SAMPLING;
    1815                 :        780 :             st->img.rect.x0 = st->img.rect.y0 = 0.0f;
    1816                 :        780 :             st->img.w = st->img.rect.x1 = src.new_w;
    1817                 :        780 :             st->img.h = st->img.rect.y1 = src.new_h;
    1818                 :        780 :             src.scale = 1.0;
    1819                 :            :         }
    1820                 :            : 
    1821                 :        802 :         pass_hook(pass, &st->img, plane_scaled_hook_stages[st->type]);
    1822                 :        802 :         ident_t sub = sh_subpass(sh, img_sh(pass, &st->img));
    1823         [ -  + ]:        802 :         if (!sub) {
    1824         [ #  # ]:          0 :             if (!img_tex(pass, &st->img)) {
    1825                 :          0 :                 pl_dispatch_abort(rr->dp, &sh);
    1826                 :          0 :                 return false;
    1827                 :            :             }
    1828                 :            : 
    1829                 :          0 :             sub = sh_subpass(sh, img_sh(pass, &st->img));
    1830         [ #  # ]:          0 :             pl_assert(sub);
    1831                 :            :         }
    1832                 :            : 
    1833                 :        802 :         GLSL("tmp = vec4("$") * "$"(); \n", SH_FLOAT(src.scale), sub);
    1834         [ +  + ]:       1609 :         for (int c = 0; c < src.components; c++) {
    1835         [ -  + ]:        807 :             if (plane->component_mapping[c] < 0)
    1836                 :          0 :                 continue;
    1837                 :        807 :             GLSL("color[%d] = tmp[%d];\n", plane->component_mapping[c],
    1838                 :            :                  st->fmt->sample_order[c]);
    1839                 :            :         }
    1840                 :            : 
    1841                 :            :         // we don't need it anymore
    1842                 :        802 :         pl_dispatch_abort(rr->dp, &st->img.sh);
    1843                 :            :     }
    1844                 :            : 
    1845                 :        797 :     GLSL("}\n");
    1846                 :            : 
    1847                 :       1594 :     pass->img = (struct img) {
    1848                 :            :         .sh     = sh,
    1849                 :            :         .w      = pl_rect_w(ref_rounded),
    1850                 :            :         .h      = pl_rect_h(ref_rounded),
    1851                 :        797 :         .repr   = ref->img.repr,
    1852                 :            :         .color  = image->color,
    1853                 :        797 :         .comps  = ref->img.repr.alpha == PL_ALPHA_NONE ? 3 : 4,
    1854                 :            :         .rect   = {
    1855                 :            :             off_x,
    1856                 :            :             off_y,
    1857                 :        797 :             off_x + pl_rect_w(ref_rc),
    1858         [ -  + ]:        797 :             off_y + pl_rect_h(ref_rc),
    1859                 :            :         },
    1860                 :            :     };
    1861                 :            : 
    1862                 :            :     // Update the reference rect to our adjusted image coordinates
    1863                 :        797 :     pass->ref_rect = pass->img.rect;
    1864                 :            : 
    1865                 :        797 :     pass_hook(pass, &pass->img, PL_HOOK_NATIVE);
    1866                 :            : 
    1867                 :            :     // Apply LUT logic and colorspace conversion
    1868                 :            :     enum pl_lut_type lut_type = guess_frame_lut_type(image, false);
    1869                 :        797 :     sh = img_sh(pass, &pass->img);
    1870                 :            :     bool needs_conversion = true;
    1871                 :            : 
    1872         [ +  + ]:        797 :     if (lut_type == PL_LUT_NATIVE || lut_type == PL_LUT_CONVERSION) {
    1873                 :            :         // Fix bit depth normalization before applying LUT
    1874                 :         24 :         float scale = pl_color_repr_normalize(&pass->img.repr);
    1875                 :         24 :         GLSL("color *= vec4("$"); \n", SH_FLOAT(scale));
    1876                 :         24 :         pl_shader_set_alpha(sh, &pass->img.repr, PL_ALPHA_INDEPENDENT);
    1877                 :         24 :         pl_shader_custom_lut(sh, image->lut, &rr->lut_state[LUT_IMAGE]);
    1878                 :            : 
    1879         [ +  + ]:         24 :         if (lut_type == PL_LUT_CONVERSION) {
    1880                 :          8 :             pass->img.repr.sys = PL_COLOR_SYSTEM_RGB;
    1881                 :          8 :             pass->img.repr.levels = PL_COLOR_LEVELS_FULL;
    1882                 :            :             needs_conversion = false;
    1883                 :            :         }
    1884                 :            :     }
    1885                 :            : 
    1886                 :            :     if (needs_conversion) {
    1887         [ -  + ]:        789 :         if (pass->img.repr.sys == PL_COLOR_SYSTEM_XYZ)
    1888                 :          0 :             pass->img.color.transfer = PL_COLOR_TRC_LINEAR;
    1889                 :        789 :         pl_shader_decode_color(sh, &pass->img.repr, params->color_adjustment);
    1890                 :            :     }
    1891                 :            : 
    1892         [ +  + ]:        797 :     if (lut_type == PL_LUT_NORMALIZED)
    1893                 :          8 :         pl_shader_custom_lut(sh, image->lut, &rr->lut_state[LUT_IMAGE]);
    1894                 :            : 
    1895                 :            :     // A main PL_LUT_CONVERSION LUT overrides ICC profiles
    1896   [ +  +  +  + ]:        797 :     bool main_lut_override = params->lut && params->lut_type == PL_LUT_CONVERSION;
    1897   [ +  +  +  - ]:        797 :     if (image->icc && !main_lut_override) {
    1898                 :          8 :         pl_shader_set_alpha(sh, &pass->img.repr, PL_ALPHA_INDEPENDENT);
    1899                 :          8 :         pl_icc_decode(sh, image->icc, &rr->icc_state[ICC_IMAGE], &pass->img.color);
    1900                 :            :     }
    1901                 :            : 
    1902                 :            :     // Pre-multiply alpha channel before the rest of the pipeline, to avoid
    1903                 :            :     // bleeding colors from transparent regions into non-transparent regions
    1904                 :        797 :     pl_shader_set_alpha(sh, &pass->img.repr, PL_ALPHA_PREMULTIPLIED);
    1905                 :            : 
    1906                 :        797 :     pass_hook(pass, &pass->img, PL_HOOK_RGB);
    1907                 :            :     sh = NULL;
    1908                 :        797 :     return true;
    1909                 :            : }
    1910                 :            : 
    1911                 :        797 : static bool pass_scale_main(struct pass_state *pass)
    1912                 :            : {
    1913                 :        797 :     const struct pl_render_params *params = pass->params;
    1914                 :        797 :     pl_renderer rr = pass->rr;
    1915                 :            : 
    1916                 :        797 :     pl_fmt fbofmt = pass->fbofmt[pass->img.comps];
    1917         [ +  + ]:        797 :     if (!fbofmt) {
    1918                 :          4 :         PL_TRACE(rr, "Skipping main scaler (no FBOs)");
    1919                 :          4 :         return true;
    1920                 :            :     }
    1921                 :            : 
    1922                 :        793 :     const pl_rect2df new_rect = {
    1923                 :        793 :         .x1 = abs(pl_rect_w(pass->dst_rect)),
    1924                 :        793 :         .y1 = abs(pl_rect_h(pass->dst_rect)),
    1925                 :            :     };
    1926                 :            : 
    1927                 :        793 :     struct img *img = &pass->img;
    1928                 :        793 :     struct pl_sample_src src = {
    1929                 :            :         .components = img->comps,
    1930                 :            :         .new_w      = pl_rect_w(new_rect),
    1931                 :            :         .new_h      = pl_rect_h(new_rect),
    1932                 :            :         .rect       = img->rect,
    1933                 :            :     };
    1934                 :            : 
    1935                 :            :     const struct pl_frame *image = &pass->image;
    1936                 :            :     bool need_fbo = false;
    1937                 :            : 
    1938                 :            :     // Force FBO indirection if this shader is non-resizable
    1939                 :            :     int out_w, out_h;
    1940   [ +  +  +  + ]:        793 :     if (img->sh && pl_shader_output_size(img->sh, &out_w, &out_h))
    1941   [ +  -  -  + ]:         22 :         need_fbo |= out_w != src.new_w || out_h != src.new_h;
    1942                 :            : 
    1943                 :        793 :     struct sampler_info info = sample_src_info(pass, &src, SAMPLER_MAIN);
    1944   [ +  +  -  + ]:        793 :     bool use_sigmoid = info.dir == SAMPLER_UP && params->sigmoid_params;
    1945                 :        793 :     bool use_linear  = info.dir == SAMPLER_DOWN;
    1946                 :            : 
    1947                 :            :     // Opportunistically update peak here if it would save performance
    1948         [ +  + ]:        793 :     if (info.dir == SAMPLER_UP)
    1949                 :        108 :         hdr_update_peak(pass);
    1950                 :            : 
    1951                 :            :     // We need to enable the full rendering pipeline if there are any user
    1952                 :            :     // shaders / hooks that might depend on it.
    1953                 :            :     uint64_t scaling_hooks = PL_HOOK_PRE_KERNEL | PL_HOOK_POST_KERNEL;
    1954                 :            :     uint64_t linear_hooks = PL_HOOK_LINEAR | PL_HOOK_SIGMOID;
    1955                 :            : 
    1956         [ +  + ]:        808 :     for (int i = 0; i < params->num_hooks; i++) {
    1957         [ -  + ]:         15 :         if (params->hooks[i]->stages & (scaling_hooks | linear_hooks)) {
    1958                 :            :             need_fbo = true;
    1959         [ #  # ]:          0 :             if (params->hooks[i]->stages & linear_hooks)
    1960                 :            :                 use_linear = true;
    1961         [ #  # ]:          0 :             if (params->hooks[i]->stages & PL_HOOK_SIGMOID)
    1962                 :            :                 use_sigmoid = true;
    1963                 :            :         }
    1964                 :            :     }
    1965                 :            : 
    1966   [ +  +  +  - ]:        793 :     if (info.dir == SAMPLER_NOOP && !need_fbo) {
    1967         [ -  + ]:        577 :         pl_assert(src.new_w == img->w && src.new_h == img->h);
    1968                 :        577 :         PL_TRACE(rr, "Skipping main scaler (would be no-op)");
    1969                 :        577 :         goto done;
    1970                 :            :     }
    1971                 :            : 
    1972   [ +  +  +  - ]:        216 :     if (info.type == SAMPLER_DIRECT && !need_fbo) {
    1973                 :         16 :         img->w = src.new_w;
    1974                 :         16 :         img->h = src.new_h;
    1975                 :         16 :         img->rect = new_rect;
    1976                 :         16 :         PL_TRACE(rr, "Skipping main scaler (free sampling)");
    1977                 :         16 :         goto done;
    1978                 :            :     }
    1979                 :            : 
    1980                 :            :     // Hard-disable both sigmoidization and linearization when required
    1981   [ +  -  -  + ]:        200 :     if (params->disable_linear_scaling || fbofmt->component_depth[0] < 16)
    1982                 :            :         use_sigmoid = use_linear = false;
    1983                 :            : 
    1984                 :            :     // Avoid sigmoidization for HDR content because it clips to [0,1], and
    1985                 :            :     // linearization because it causes very nasty ringing artefacts.
    1986         [ -  + ]:        200 :     if (pl_color_space_is_hdr(&img->color))
    1987                 :            :         use_sigmoid = use_linear = false;
    1988                 :            : 
    1989   [ -  +  -  - ]:        200 :     if (!(use_linear || use_sigmoid) && img->color.transfer == PL_COLOR_TRC_LINEAR) {
    1990                 :          0 :         img->color.transfer = image->color.transfer;
    1991         [ #  # ]:          0 :         if (image->color.transfer == PL_COLOR_TRC_LINEAR)
    1992                 :          0 :             img->color.transfer = PL_COLOR_TRC_GAMMA22; // arbitrary fallback
    1993                 :          0 :         pl_shader_delinearize(img_sh(pass, img), &img->color);
    1994                 :            :     }
    1995                 :            : 
    1996         [ +  - ]:        200 :     if (use_linear || use_sigmoid) {
    1997                 :        200 :         pl_shader_linearize(img_sh(pass, img), &img->color);
    1998                 :        200 :         img->color.transfer = PL_COLOR_TRC_LINEAR;
    1999                 :        200 :         pass_hook(pass, img, PL_HOOK_LINEAR);
    2000                 :            :     }
    2001                 :            : 
    2002         [ +  + ]:        200 :     if (use_sigmoid) {
    2003                 :         96 :         pl_shader_sigmoidize(img_sh(pass, img), params->sigmoid_params);
    2004                 :         96 :         pass_hook(pass, img, PL_HOOK_SIGMOID);
    2005                 :            :     }
    2006                 :            : 
    2007                 :        200 :     pass_hook(pass, img, PL_HOOK_PRE_KERNEL);
    2008                 :            : 
    2009                 :        200 :     src.tex = img_tex(pass, img);
    2010         [ +  - ]:        200 :     if (!src.tex)
    2011                 :            :         return false;
    2012                 :        200 :     pass->need_peak_fbo = false;
    2013                 :            : 
    2014                 :        200 :     pl_shader sh = pl_dispatch_begin_ex(rr->dp, true);
    2015                 :        200 :     dispatch_sampler(pass, sh, &rr->sampler_main, SAMPLER_MAIN, NULL, &src);
    2016                 :        200 :     img->tex  = NULL;
    2017                 :        200 :     img->sh   = sh;
    2018                 :        200 :     img->w    = src.new_w;
    2019                 :        200 :     img->h    = src.new_h;
    2020                 :        200 :     img->rect = new_rect;
    2021                 :            : 
    2022                 :        200 :     pass_hook(pass, img, PL_HOOK_POST_KERNEL);
    2023                 :            : 
    2024         [ +  + ]:        200 :     if (use_sigmoid)
    2025                 :         96 :         pl_shader_unsigmoidize(img_sh(pass, img), params->sigmoid_params);
    2026                 :            : 
    2027                 :        104 : done:
    2028         [ +  + ]:        793 :     if (info.dir != SAMPLER_UP)
    2029                 :        685 :         hdr_update_peak(pass);
    2030                 :        793 :     pass_hook(pass, img, PL_HOOK_SCALED);
    2031                 :        793 :     return true;
    2032                 :            : }
    2033                 :            : 
    2034                 :        789 : static pl_tex get_feature_map(struct pass_state *pass)
    2035                 :            : {
    2036                 :        789 :     const struct pl_render_params *params = pass->params;
    2037                 :        789 :     pl_renderer rr = pass->rr;
    2038                 :        789 :     const struct pl_color_map_params *cparams = params->color_map_params;
    2039         [ +  + ]:        789 :     cparams = PL_DEF(cparams, &pl_color_map_default_params);
    2040   [ -  +  -  - ]:        789 :     if (!cparams->contrast_recovery || cparams->contrast_smoothness <= 1)
    2041                 :            :         return NULL;
    2042         [ #  # ]:          0 :     if (!pass->fbofmt[4])
    2043                 :            :         return NULL;
    2044         [ #  # ]:          0 :     if (!pl_color_space_is_hdr(&pass->img.color))
    2045                 :            :         return NULL;
    2046         [ #  # ]:          0 :     if (rr->errors & (PL_RENDER_ERR_SAMPLING | PL_RENDER_ERR_CONTRAST_RECOVERY))
    2047                 :            :         return NULL;
    2048         [ #  # ]:          0 :     if (pass->img.color.hdr.max_luma <= pass->target.color.hdr.max_luma + 1e-6)
    2049                 :            :         return NULL; // no adaptation needed
    2050   [ #  #  #  # ]:          0 :     if (params->lut && params->lut_type == PL_LUT_CONVERSION)
    2051                 :            :         return NULL; // LUT handles tone mapping
    2052                 :            : 
    2053                 :          0 :     struct img *img = &pass->img;
    2054         [ #  # ]:          0 :     if (!img_tex(pass, img))
    2055                 :            :         return NULL;
    2056                 :            : 
    2057                 :          0 :     const float ratio = cparams->contrast_smoothness;
    2058                 :          0 :     const int cr_w = ceilf(abs(pl_rect_w(pass->dst_rect)) / ratio);
    2059                 :          0 :     const int cr_h = ceilf(abs(pl_rect_h(pass->dst_rect)) / ratio);
    2060                 :          0 :     pl_tex inter_tex = get_fbo(pass, img->w, img->h, NULL, 1, PL_DEBUG_TAG);
    2061                 :          0 :     pl_tex out_tex   = get_fbo(pass, cr_w, cr_h, NULL, 1, PL_DEBUG_TAG);
    2062         [ #  # ]:          0 :     if (!inter_tex || !out_tex)
    2063                 :          0 :         goto error;
    2064                 :            : 
    2065                 :          0 :     pl_shader sh = pl_dispatch_begin(rr->dp);
    2066                 :          0 :     pl_shader_sample_direct(sh, pl_sample_src( .tex = img->tex ));
    2067                 :          0 :     pl_shader_extract_features(sh, img->color);
    2068                 :          0 :     bool ok = pl_dispatch_finish(rr->dp, pl_dispatch_params(
    2069                 :            :         .shader = &sh,
    2070                 :            :         .target = inter_tex,
    2071                 :            :     ));
    2072         [ #  # ]:          0 :     if (!ok)
    2073                 :          0 :         goto error;
    2074                 :            : 
    2075                 :          0 :     const struct pl_sample_src src = {
    2076                 :            :         .tex          = inter_tex,
    2077                 :            :         .rect         = img->rect,
    2078                 :            :         .address_mode = PL_TEX_ADDRESS_MIRROR,
    2079                 :            :         .components   = 1,
    2080                 :            :         .new_w        = cr_w,
    2081                 :            :         .new_h        = cr_h,
    2082                 :            :     };
    2083                 :            : 
    2084                 :          0 :     sh = pl_dispatch_begin(rr->dp);
    2085                 :          0 :     dispatch_sampler(pass, sh, &rr->sampler_contrast, SAMPLER_CONTRAST, out_tex, &src);
    2086                 :          0 :     ok = pl_dispatch_finish(rr->dp, pl_dispatch_params(
    2087                 :            :         .shader = &sh,
    2088                 :            :         .target = out_tex,
    2089                 :            :     ));
    2090         [ #  # ]:          0 :     if (!ok)
    2091                 :          0 :         goto error;
    2092                 :            : 
    2093                 :            :     return out_tex;
    2094                 :            : 
    2095                 :          0 : error:
    2096                 :          0 :     PL_ERR(rr, "Failed extracting luma for contrast recovery, disabling");
    2097                 :          0 :     rr->errors |= PL_RENDER_ERR_CONTRAST_RECOVERY;
    2098                 :          0 :     return NULL;
    2099                 :            : }
    2100                 :            : 
    2101                 :            : // Transforms image into the output color space (tone-mapping, ICC 3DLUT, etc)
    2102                 :        797 : static void pass_convert_colors(struct pass_state *pass)
    2103                 :            : {
    2104                 :        797 :     const struct pl_render_params *params = pass->params;
    2105                 :            :     const struct pl_frame *image = &pass->image;
    2106                 :            :     const struct pl_frame *target = &pass->target;
    2107                 :        797 :     pl_renderer rr = pass->rr;
    2108                 :            : 
    2109                 :        797 :     struct img *img = &pass->img;
    2110                 :        797 :     pl_shader sh = img_sh(pass, img);
    2111                 :            : 
    2112                 :            :     bool prelinearized = false;
    2113                 :            :     bool need_conversion = true;
    2114         [ -  + ]:        797 :     assert(image->color.primaries == img->color.primaries);
    2115         [ +  + ]:        797 :     if (img->color.transfer == PL_COLOR_TRC_LINEAR) {
    2116         [ -  + ]:        208 :         if (img->repr.alpha == PL_ALPHA_PREMULTIPLIED) {
    2117                 :            :             // Very annoying edge case: since prelinerization happens with
    2118                 :            :             // premultiplied alpha, but color mapping happens with independent
    2119                 :            :             // alpha, we need to go back to non-linear representation *before*
    2120                 :            :             // alpha mode conversion, to avoid distortion
    2121                 :          0 :             img->color.transfer = image->color.transfer;
    2122                 :          0 :             pl_shader_delinearize(sh, &img->color);
    2123                 :            :         } else {
    2124                 :            :             prelinearized = true;
    2125                 :            :         }
    2126         [ +  - ]:        589 :     } else if (img->color.transfer != image->color.transfer) {
    2127         [ #  # ]:          0 :         if (image->color.transfer == PL_COLOR_TRC_LINEAR) {
    2128                 :            :             // Another annoying edge case: if the input is linear light, but we
    2129                 :            :             // decide to un-linearize it for scaling purposes, we need to
    2130                 :            :             // re-linearize before passing it into `pl_shader_color_map`
    2131                 :          0 :             pl_shader_linearize(sh, &img->color);
    2132                 :          0 :             img->color.transfer = PL_COLOR_TRC_LINEAR;
    2133                 :            :         }
    2134                 :            :     }
    2135                 :            : 
    2136                 :            :     // Do all processing in independent alpha, to avoid nonlinear distortions
    2137                 :        797 :     pl_shader_set_alpha(sh, &img->repr, PL_ALPHA_INDEPENDENT);
    2138                 :            : 
    2139                 :            :     // Apply color blindness simulation if requested
    2140         [ +  + ]:        797 :     if (params->cone_params)
    2141                 :          4 :         pl_shader_cone_distort(sh, img->color, params->cone_params);
    2142                 :            : 
    2143         [ +  + ]:        797 :     if (params->lut) {
    2144                 :         32 :         struct pl_color_space lut_in = params->lut->color_in;
    2145                 :         32 :         struct pl_color_space lut_out = params->lut->color_out;
    2146   [ -  +  +  + ]:         32 :         switch (params->lut_type) {
    2147                 :         16 :         case PL_LUT_UNKNOWN:
    2148                 :            :         case PL_LUT_NATIVE:
    2149                 :         16 :             pl_color_space_merge(&lut_in, &image->color);
    2150                 :         16 :             pl_color_space_merge(&lut_out, &image->color);
    2151                 :         16 :             break;
    2152                 :          8 :         case PL_LUT_CONVERSION:
    2153                 :          8 :             pl_color_space_merge(&lut_in, &image->color);
    2154                 :            :             need_conversion = false; // conversion LUT the highest priority
    2155                 :          8 :             break;
    2156                 :          8 :         case PL_LUT_NORMALIZED:
    2157         [ +  - ]:          8 :             if (!prelinearized) {
    2158                 :            :                 // PL_LUT_NORMALIZED wants linear input data
    2159                 :          8 :                 pl_shader_linearize(sh, &img->color);
    2160                 :          8 :                 img->color.transfer = PL_COLOR_TRC_LINEAR;
    2161                 :            :                 prelinearized = true;
    2162                 :            :             }
    2163                 :          8 :             pl_color_space_merge(&lut_in, &img->color);
    2164                 :          8 :             pl_color_space_merge(&lut_out, &img->color);
    2165                 :          8 :             break;
    2166                 :            :         }
    2167                 :            : 
    2168                 :         32 :         pl_shader_color_map_ex(sh, params->color_map_params, pl_color_map_args(
    2169                 :            :             .src = image->color,
    2170                 :            :             .dst = lut_in,
    2171                 :            :             .prelinearized = prelinearized,
    2172                 :            :         ));
    2173                 :            : 
    2174         [ +  + ]:         32 :         if (params->lut_type == PL_LUT_NORMALIZED) {
    2175                 :          8 :             GLSLF("color.rgb *= vec3(1.0/"$"); \n",
    2176                 :            :                   SH_FLOAT(pl_color_transfer_nominal_peak(lut_in.transfer)));
    2177                 :            :         }
    2178                 :            : 
    2179                 :         32 :         pl_shader_custom_lut(sh, params->lut, &rr->lut_state[LUT_PARAMS]);
    2180                 :            : 
    2181         [ +  + ]:         32 :         if (params->lut_type == PL_LUT_NORMALIZED) {
    2182                 :          8 :             GLSLF("color.rgb *= vec3("$"); \n",
    2183                 :            :                   SH_FLOAT(pl_color_transfer_nominal_peak(lut_out.transfer)));
    2184                 :            :         }
    2185                 :            : 
    2186         [ +  + ]:         32 :         if (params->lut_type != PL_LUT_CONVERSION) {
    2187                 :         24 :             pl_shader_color_map_ex(sh, params->color_map_params, pl_color_map_args(
    2188                 :            :                 .src = lut_out,
    2189                 :            :                 .dst = img->color,
    2190                 :            :             ));
    2191                 :            :         }
    2192                 :            :     }
    2193                 :            : 
    2194         [ +  + ]:         32 :     if (need_conversion) {
    2195                 :        789 :         struct pl_color_space target_csp = target->color;
    2196         [ +  + ]:        789 :         if (target->icc)
    2197                 :            :             target_csp.transfer = PL_COLOR_TRC_LINEAR;
    2198                 :            : 
    2199   [ +  +  -  + ]:        789 :         if (pass->need_peak_fbo && !img_tex(pass, img))
    2200                 :          0 :             return;
    2201                 :            : 
    2202                 :            :         // generate HDR feature map if required
    2203                 :        789 :         pl_tex feature_map = get_feature_map(pass);
    2204                 :        789 :         sh = img_sh(pass, img); // `get_feature_map` dispatches previous shader
    2205                 :            : 
    2206                 :            :         // current -> target
    2207                 :        789 :         pl_shader_color_map_ex(sh, params->color_map_params, pl_color_map_args(
    2208                 :            :             .src           = image->color,
    2209                 :            :             .dst           = target_csp,
    2210                 :            :             .prelinearized = prelinearized,
    2211                 :            :             .state         = &rr->tone_map_state,
    2212                 :            :             .feature_map   = feature_map,
    2213                 :            :         ));
    2214                 :            : 
    2215         [ +  + ]:        789 :         if (target->icc)
    2216                 :          8 :             pl_icc_encode(sh, target->icc, &rr->icc_state[ICC_TARGET]);
    2217                 :            :     }
    2218                 :            : 
    2219                 :            :     enum pl_lut_type lut_type = guess_frame_lut_type(target, true);
    2220         [ +  + ]:        797 :     if (lut_type == PL_LUT_NORMALIZED || lut_type == PL_LUT_CONVERSION)
    2221                 :         16 :         pl_shader_custom_lut(sh, target->lut, &rr->lut_state[LUT_TARGET]);
    2222                 :            : 
    2223                 :        797 :     img->color = target->color;
    2224                 :            : }
    2225                 :            : 
    2226                 :            : // Returns true if error diffusion was successfully performed
    2227                 :        142 : static bool pass_error_diffusion(struct pass_state *pass, pl_shader *sh,
    2228                 :            :                                  int new_depth, int comps, int out_w, int out_h)
    2229                 :            : {
    2230                 :        142 :     const struct pl_render_params *params = pass->params;
    2231                 :        142 :     pl_renderer rr = pass->rr;
    2232   [ -  +  -  - ]:        142 :     if (!params->error_diffusion || (rr->errors & PL_RENDER_ERR_ERROR_DIFFUSION))
    2233                 :            :         return false;
    2234                 :            : 
    2235                 :          0 :     size_t shmem_req = pl_error_diffusion_shmem_req(params->error_diffusion, out_h);
    2236         [ #  # ]:          0 :     if (shmem_req > rr->gpu->glsl.max_shmem_size) {
    2237                 :          0 :         PL_TRACE(rr, "Disabling error diffusion due to shmem requirements (%zu) "
    2238                 :            :                  "exceeding capabilities (%zu)", shmem_req, rr->gpu->glsl.max_shmem_size);
    2239                 :          0 :         return false;
    2240                 :            :     }
    2241                 :            : 
    2242                 :          0 :     pl_fmt fmt = pass->fbofmt[comps];
    2243   [ #  #  #  # ]:          0 :     if (!fmt || !(fmt->caps & PL_FMT_CAP_STORABLE)) {
    2244                 :          0 :         PL_ERR(rr, "Error diffusion requires storable FBOs but GPU does not "
    2245                 :            :                "provide them... disabling!");
    2246                 :          0 :         goto error;
    2247                 :            :     }
    2248                 :            : 
    2249                 :          0 :     struct pl_error_diffusion_params edpars = {
    2250                 :            :         .new_depth = new_depth,
    2251                 :          0 :         .kernel = params->error_diffusion,
    2252                 :            :     };
    2253                 :            : 
    2254                 :            :     // Create temporary framebuffers
    2255                 :          0 :     edpars.input_tex = get_fbo(pass, out_w, out_h, fmt, comps, PL_DEBUG_TAG);
    2256                 :          0 :     edpars.output_tex = get_fbo(pass, out_w, out_h, fmt, comps, PL_DEBUG_TAG);
    2257   [ #  #  #  # ]:          0 :     if (!edpars.input_tex || !edpars.output_tex)
    2258                 :          0 :         goto error;
    2259                 :            : 
    2260                 :          0 :     pl_shader dsh = pl_dispatch_begin(rr->dp);
    2261         [ #  # ]:          0 :     if (!pl_shader_error_diffusion(dsh, &edpars)) {
    2262                 :          0 :         pl_dispatch_abort(rr->dp, &dsh);
    2263                 :          0 :         goto error;
    2264                 :            :     }
    2265                 :            : 
    2266                 :            :     // Everything was okay, run the shaders
    2267                 :          0 :     bool ok = pl_dispatch_finish(rr->dp, pl_dispatch_params(
    2268                 :            :         .shader = sh,
    2269                 :            :         .target = edpars.input_tex,
    2270                 :            :     ));
    2271                 :            : 
    2272         [ #  # ]:          0 :     if (ok) {
    2273                 :          0 :         ok = pl_dispatch_compute(rr->dp, pl_dispatch_compute_params(
    2274                 :            :             .shader = &dsh,
    2275                 :            :             .dispatch_size = {1, 1, 1},
    2276                 :            :         ));
    2277                 :            :     }
    2278                 :            : 
    2279                 :          0 :     *sh = pl_dispatch_begin(rr->dp);
    2280         [ #  # ]:          0 :     pl_shader_sample_direct(*sh, pl_sample_src(
    2281                 :            :         .tex = ok ? edpars.output_tex : edpars.input_tex,
    2282                 :            :     ));
    2283                 :          0 :     return ok;
    2284                 :            : 
    2285                 :          0 : error:
    2286                 :          0 :     rr->errors |= PL_RENDER_ERR_ERROR_DIFFUSION;
    2287                 :          0 :     return false;
    2288                 :            : }
    2289                 :            : 
    2290                 :            : #define CLEAR_COL(params)                                                       \
    2291                 :            :     (float[4]) {                                                                \
    2292                 :            :         (params)->background_color[0],                                          \
    2293                 :            :         (params)->background_color[1],                                          \
    2294                 :            :         (params)->background_color[2],                                          \
    2295                 :            :         1.0 - (params)->background_transparency,                                \
    2296                 :            :     }
    2297                 :            : 
    2298                 :        120 : static void clear_target(pl_renderer rr, const struct pl_frame *target,
    2299                 :            :                          const struct pl_render_params *params)
    2300                 :            : {
    2301                 :        120 :     enum pl_clear_mode border = params->border;
    2302         [ +  - ]:        120 :     if (params->skip_target_clearing)
    2303                 :            :         border = PL_CLEAR_SKIP;
    2304                 :            : 
    2305   [ +  -  -  -  :        120 :     switch (border) {
                      - ]
    2306                 :        120 :     case PL_CLEAR_COLOR:
    2307                 :        120 :         pl_frame_clear_rgba(rr->gpu, target, CLEAR_COL(params));
    2308                 :        120 :         break;
    2309                 :          0 :     case PL_CLEAR_TILES:
    2310                 :          0 :         pl_frame_clear_tiles(rr->gpu, target, params->tile_colors, params->tile_size);
    2311                 :          0 :         break;
    2312                 :            :     case PL_CLEAR_SKIP: break;
    2313                 :          0 :     case PL_CLEAR_MODE_COUNT: pl_unreachable();
    2314                 :            :     }
    2315                 :        120 : }
    2316                 :            : 
    2317                 :       1077 : static bool pass_output_target(struct pass_state *pass)
    2318                 :            : {
    2319                 :       1077 :     const struct pl_render_params *params = pass->params;
    2320                 :            :     const struct pl_frame *image = &pass->image;
    2321                 :       1077 :     const struct pl_frame *target = &pass->target;
    2322                 :       1077 :     pl_renderer rr = pass->rr;
    2323                 :            : 
    2324                 :       1077 :     struct img *img = &pass->img;
    2325                 :       1077 :     pl_shader sh = img_sh(pass, img);
    2326                 :            : 
    2327         [ +  + ]:       1077 :     if (params->corner_rounding > 0.0f) {
    2328                 :          4 :         const float out_w2 = fabsf(pl_rect_w(target->crop)) / 2.0f;
    2329                 :          4 :         const float out_h2 = fabsf(pl_rect_h(target->crop)) / 2.0f;
    2330                 :          4 :         const float radius = fminf(params->corner_rounding, 1.0f) *
    2331                 :          4 :                              fminf(out_w2, out_h2);
    2332                 :          4 :         const struct pl_rect2df relpos = {
    2333                 :          4 :             .x0 = -out_w2, .y0 = -out_h2,
    2334                 :            :             .x1 =  out_w2, .y1 =  out_h2,
    2335                 :            :         };
    2336                 :          4 :         GLSL("float radius = "$";                           \n"
    2337                 :            :              "vec2 size2 = vec2("$", "$");                  \n"
    2338                 :            :              "vec2 relpos = "$";                            \n"
    2339                 :            :              "vec2 rd = abs(relpos) - size2 + vec2(radius); \n"
    2340                 :            :              "float rdist = length(max(rd, 0.0)) - radius;  \n"
    2341                 :            :              "float border = smoothstep(2.0f, 0.0f, rdist); \n",
    2342                 :            :              SH_FLOAT_DYN(radius),
    2343                 :            :              SH_FLOAT_DYN(out_w2), SH_FLOAT_DYN(out_h2),
    2344                 :            :              sh_attr_vec2(sh, "relpos", &relpos));
    2345                 :            : 
    2346   [ +  -  -  -  :          4 :         switch (img->repr.alpha) {
                      - ]
    2347                 :            :         case PL_ALPHA_UNKNOWN:
    2348                 :            :         case PL_ALPHA_NONE:
    2349                 :          4 :             GLSL("color.a = border; \n");
    2350                 :          4 :             img->repr.alpha = PL_ALPHA_INDEPENDENT;
    2351                 :          4 :             img->comps = 4;
    2352                 :          4 :             break;
    2353                 :            :         case PL_ALPHA_INDEPENDENT:
    2354                 :          0 :             GLSL("color.a *= border; \n");
    2355                 :          0 :             break;
    2356                 :            :         case PL_ALPHA_PREMULTIPLIED:
    2357                 :          0 :             GLSL("color *= border; \n");
    2358                 :          0 :             break;
    2359                 :            :         case PL_ALPHA_MODE_COUNT:
    2360                 :          0 :             pl_unreachable();
    2361                 :            :         }
    2362                 :            :     }
    2363                 :            : 
    2364                 :       1077 :     const struct pl_plane *ref = &target->planes[pass->dst_ref];
    2365                 :       1077 :     pl_rect2d dst_rect = pass->dst_rect;
    2366         [ +  + ]:       1077 :     if (params->distort_params) {
    2367                 :         24 :         struct pl_distort_params dpars = *params->distort_params;
    2368         [ +  + ]:         24 :         if (dpars.alpha_mode) {
    2369                 :          4 :             pl_shader_set_alpha(sh, &img->repr, dpars.alpha_mode);
    2370                 :          4 :             img->repr.alpha = dpars.alpha_mode;
    2371                 :          4 :             img->comps = 4;
    2372                 :            :         }
    2373                 :         24 :         pl_tex tex = img_tex(pass, img);
    2374         [ -  + ]:         24 :         if (!tex)
    2375                 :          0 :             return false;
    2376                 :            :         // Expand canvas to fit result of distortion
    2377                 :         24 :         const float ar = pl_rect2df_aspect(&target->crop);
    2378                 :         24 :         const float sx = fminf(ar, 1.0f);
    2379                 :         24 :         const float sy = fminf(1.0f / ar, 1.0f);
    2380                 :         24 :         pl_rect2df bb = pl_transform2x2_bounds(&dpars.transform, &(pl_rect2df) {
    2381                 :         24 :             .x0 = -sx, .x1 = sx,
    2382                 :         24 :             .y0 = -sy, .y1 = sy,
    2383                 :            :         });
    2384                 :            : 
    2385                 :            :         // Clamp to output size and adjust as needed when constraining output
    2386                 :         24 :         pl_rect2df tmp = target->crop;
    2387                 :         24 :         pl_rect2df_stretch(&tmp, pl_rect_w(bb) / (2*sx), pl_rect_h(bb) / (2*sy));
    2388                 :         24 :         const float tmp_w = pl_rect_w(tmp), tmp_h = pl_rect_h(tmp);
    2389                 :         24 :         int canvas_w = ref->texture->params.w,
    2390                 :         24 :             canvas_h = ref->texture->params.h;
    2391         [ -  + ]:         24 :         if (pass->rotation % PL_ROTATION_180 == PL_ROTATION_90)
    2392                 :            :             PL_SWAP(canvas_w, canvas_h);
    2393   [ +  -  +  - ]:         24 :         tmp.x0 = PL_CLAMP(tmp.x0, 0.0f, canvas_w);
    2394   [ +  -  +  - ]:         24 :         tmp.x1 = PL_CLAMP(tmp.x1, 0.0f, canvas_w);
    2395   [ +  -  +  - ]:         24 :         tmp.y0 = PL_CLAMP(tmp.y0, 0.0f, canvas_h);
    2396   [ +  -  +  - ]:         24 :         tmp.y1 = PL_CLAMP(tmp.y1, 0.0f, canvas_h);
    2397         [ +  + ]:         24 :         if (dpars.constrain) {
    2398                 :          4 :             const float rx = pl_rect_w(tmp) / tmp_w;
    2399                 :          4 :             const float ry = pl_rect_h(tmp) / tmp_h;
    2400                 :          4 :             pl_rect2df_stretch(&tmp, fminf(ry / rx, 1.0f), fminf(rx / ry, 1.0f));
    2401                 :            :         }
    2402                 :         24 :         dst_rect.x0 = roundf(tmp.x0);
    2403                 :         24 :         dst_rect.x1 = roundf(tmp.x1);
    2404                 :         24 :         dst_rect.y0 = roundf(tmp.y0);
    2405                 :         24 :         dst_rect.y1 = roundf(tmp.y1);
    2406                 :         24 :         dpars.unscaled = true;
    2407                 :         24 :         img->w = abs(pl_rect_w(dst_rect));
    2408                 :         24 :         img->h = abs(pl_rect_h(dst_rect));
    2409                 :         24 :         img->tex = NULL;
    2410                 :         24 :         img->sh = sh = pl_dispatch_begin(rr->dp);
    2411                 :         24 :         pl_shader_distort(sh, tex, img->w, img->h, &dpars);
    2412                 :            :     }
    2413                 :            : 
    2414                 :       1077 :     pass_hook(pass, img, PL_HOOK_PRE_OUTPUT);
    2415                 :            : 
    2416                 :       1077 :     enum pl_clear_mode background = params->background;
    2417         [ +  + ]:       1077 :     if (params->blend_against_tiles)
    2418                 :            :         background = PL_CLEAR_TILES;
    2419         [ -  + ]:       1073 :     else if (params->skip_target_clearing)
    2420                 :            :         background = PL_CLEAR_SKIP;
    2421                 :            : 
    2422   [ +  -  -  + ]:       1077 :     bool has_alpha = target->repr.alpha != PL_ALPHA_NONE || params->blend_params;
    2423                 :       1077 :     bool need_blend = background != PL_CLEAR_SKIP || !has_alpha;
    2424   [ +  +  +  - ]:       1077 :     if (img->comps == 4 && need_blend) {
    2425                 :         10 :         pl_shader_set_alpha(sh, &img->repr, PL_ALPHA_PREMULTIPLIED);
    2426   [ +  +  -  - ]:         10 :         switch (background) {
    2427                 :            :         case PL_CLEAR_COLOR:
    2428                 :          6 :             GLSL("color += (1.0 - color.a) * vec4("$", "$", "$", "$"); \n",
    2429                 :            :                  SH_FLOAT(params->background_color[0]),
    2430                 :            :                  SH_FLOAT(params->background_color[1]),
    2431                 :            :                  SH_FLOAT(params->background_color[2]),
    2432                 :            :                  SH_FLOAT(1.0 - params->background_transparency));
    2433         [ +  - ]:          6 :             if (!params->background_transparency) {
    2434                 :          6 :                 img->repr.alpha = PL_ALPHA_NONE;
    2435                 :          6 :                 img->comps = 3;
    2436                 :            :             }
    2437                 :            :             break;
    2438                 :          4 :         case PL_CLEAR_TILES:;
    2439                 :            :             static const float zero[2][3] = {0};
    2440                 :          4 :             const float (*color)[3] = params->tile_colors;
    2441         [ -  + ]:          4 :             if (memcmp(color, zero, sizeof(zero)) == 0)
    2442                 :            :                 color = pl_render_default_params.tile_colors;
    2443         [ +  - ]:          4 :             GLSL("vec2 outcoord = gl_FragCoord.xy * "$";                    \n"
    2444                 :            :                  "bvec2 tile = lessThan(fract(outcoord), vec2(0.5));        \n"
    2445                 :            :                  "vec3 tile_color = tile.x == tile.y ? vec3("$", "$", "$")  \n"
    2446                 :            :                  "                                   : vec3("$", "$", "$"); \n"
    2447                 :            :                  "color.rgb += (1.0 - color.a) * tile_color;                \n"
    2448                 :            :                  "color.a = 1.0;                                            \n",
    2449                 :            :                  SH_FLOAT(1.0 / PL_DEF(params->tile_size, pl_render_default_params.tile_size)),
    2450                 :            :                  SH_FLOAT(color[0][0]), SH_FLOAT(color[0][1]), SH_FLOAT(color[0][2]),
    2451                 :            :                  SH_FLOAT(color[1][0]), SH_FLOAT(color[1][1]), SH_FLOAT(color[1][2]));
    2452                 :          4 :             img->repr.alpha = PL_ALPHA_NONE;
    2453                 :          4 :             img->comps = 3;
    2454                 :          4 :             break;
    2455                 :            :         case PL_CLEAR_SKIP: break;
    2456                 :          0 :         case PL_CLEAR_MODE_COUNT: pl_unreachable();
    2457                 :            :         }
    2458   [ -  +  -  - ]:       1067 :     } else if (img->comps == 4 && has_alpha) {
    2459                 :          0 :         pl_shader_set_alpha(sh, &img->repr, target->repr.alpha);
    2460                 :            :     }
    2461                 :            : 
    2462                 :            :     // Apply the color scale separately, after encoding is done, to make sure
    2463                 :            :     // that the intermediate FBO (if any) has the correct precision.
    2464                 :       1077 :     struct pl_color_repr repr = target->repr;
    2465                 :       1077 :     float scale = pl_color_repr_normalize(&repr);
    2466                 :            :     enum pl_lut_type lut_type = guess_frame_lut_type(target, true);
    2467         [ +  + ]:         24 :     if (lut_type != PL_LUT_CONVERSION)
    2468                 :       1069 :         pl_shader_encode_color(sh, &repr);
    2469         [ +  + ]:       1069 :     if (lut_type == PL_LUT_NATIVE) {
    2470                 :         16 :         pl_shader_set_alpha(sh, &img->repr, PL_ALPHA_INDEPENDENT);
    2471                 :         16 :         pl_shader_custom_lut(sh, target->lut, &rr->lut_state[LUT_TARGET]);
    2472                 :         16 :         pl_shader_set_alpha(sh, &img->repr, target->repr.alpha);
    2473                 :            :     }
    2474                 :            : 
    2475                 :            :     // Rotation handling
    2476         [ +  + ]:       1077 :     if (pass->rotation % PL_ROTATION_180 == PL_ROTATION_90) {
    2477                 :            :         PL_SWAP(dst_rect.x0, dst_rect.y0);
    2478                 :            :         PL_SWAP(dst_rect.x1, dst_rect.y1);
    2479                 :        616 :         PL_SWAP(img->w, img->h);
    2480                 :        616 :         sh->transpose = true;
    2481                 :            :     }
    2482                 :            : 
    2483                 :       1077 :     pass_hook(pass, img, PL_HOOK_OUTPUT);
    2484                 :       1077 :     sh = NULL;
    2485                 :            : 
    2486                 :            :     bool flipped_x = dst_rect.x1 < dst_rect.x0,
    2487                 :            :          flipped_y = dst_rect.y1 < dst_rect.y0;
    2488                 :            : 
    2489         [ +  + ]:       1077 :     if (pl_frame_is_cropped(target))
    2490                 :        108 :         clear_target(rr, target, params);
    2491                 :            : 
    2492         [ +  + ]:       2164 :     for (int p = 0; p < target->num_planes; p++) {
    2493                 :            :         const struct pl_plane *plane = &target->planes[p];
    2494                 :       1087 :         float rx = (float) plane->texture->params.w / ref->texture->params.w,
    2495                 :       1087 :               ry = (float) plane->texture->params.h / ref->texture->params.h;
    2496                 :            : 
    2497                 :            :         // Only accept integer scaling ratios. This accounts for the fact
    2498                 :            :         // that fractionally subsampled planes get rounded up to the
    2499                 :            :         // nearest integer size, which we want to over-render.
    2500         [ +  + ]:       1087 :         float rrx = rx >= 1 ? roundf(rx) : 1.0 / roundf(1.0 / rx),
    2501         [ +  + ]:       1087 :               rry = ry >= 1 ? roundf(ry) : 1.0 / roundf(1.0 / ry);
    2502                 :       1087 :         float sx = plane->shift_x, sy = plane->shift_y;
    2503                 :            : 
    2504                 :       1087 :         pl_rect2df plane_rectf = {
    2505                 :       1087 :             .x0 = (dst_rect.x0 - sx) * rrx,
    2506                 :       1087 :             .y0 = (dst_rect.y0 - sy) * rry,
    2507                 :       1087 :             .x1 = (dst_rect.x1 - sx) * rrx,
    2508                 :       1087 :             .y1 = (dst_rect.y1 - sy) * rry,
    2509                 :            :         };
    2510                 :            : 
    2511                 :            :         // Normalize to make the math easier
    2512                 :       1087 :         pl_rect2df_normalize(&plane_rectf);
    2513                 :            : 
    2514                 :            :         // Round the output rect
    2515                 :       1087 :         int rx0 = floorf(plane_rectf.x0), ry0 = floorf(plane_rectf.y0),
    2516                 :       1087 :             rx1 =  ceilf(plane_rectf.x1), ry1 =  ceilf(plane_rectf.y1);
    2517                 :            : 
    2518                 :       1087 :         PL_TRACE(rr, "Subsampled target %d: {%f %f %f %f} -> {%d %d %d %d}",
    2519                 :            :                  p, plane_rectf.x0, plane_rectf.y0,
    2520                 :            :                  plane_rectf.x1, plane_rectf.y1,
    2521                 :            :                  rx0, ry0, rx1, ry1);
    2522                 :            : 
    2523         [ +  + ]:       1087 :         if (target->num_planes > 1) {
    2524                 :            : 
    2525                 :            :             // Planar output, so we need to sample from an intermediate FBO
    2526                 :         30 :             struct pl_sample_src src = {
    2527                 :         15 :                 .tex        = img_tex(pass, img),
    2528                 :         15 :                 .new_w      = rx1 - rx0,
    2529                 :         15 :                 .new_h      = ry1 - ry0,
    2530                 :            :                 .rect = {
    2531                 :         15 :                     .x0 = (rx0 - plane_rectf.x0) / rrx,
    2532                 :         15 :                     .x1 = (rx1 - plane_rectf.x0) / rrx,
    2533                 :         15 :                     .y0 = (ry0 - plane_rectf.y0) / rry,
    2534                 :         15 :                     .y1 = (ry1 - plane_rectf.y0) / rry,
    2535                 :            :                 },
    2536                 :            :             };
    2537                 :            : 
    2538         [ -  + ]:         15 :             if (!src.tex) {
    2539                 :          0 :                 PL_ERR(rr, "Output requires multiple planes, but FBOs are "
    2540                 :            :                        "unavailable. This combination is unsupported.");
    2541                 :          0 :                 return false;
    2542                 :            :             }
    2543                 :            : 
    2544                 :         15 :             PL_TRACE(rr, "Sampling %dx%d img aligned from {%f %f %f %f}",
    2545                 :            :                      pass->img.w, pass->img.h,
    2546                 :            :                      src.rect.x0, src.rect.y0,
    2547                 :            :                      src.rect.x1, src.rect.y1);
    2548                 :            : 
    2549         [ +  + ]:         30 :             for (int c = 0; c < plane->components; c++) {
    2550         [ -  + ]:         15 :                 if (plane->component_mapping[c] < 0)
    2551                 :          0 :                     continue;
    2552                 :         15 :                 src.component_mask |= 1 << plane->component_mapping[c];
    2553                 :            :             }
    2554                 :            : 
    2555         [ -  + ]:         15 :             if (params->blend_params) /* preserve alpha if blending */
    2556                 :          0 :                 src.component_mask |= 1 << PL_CHANNEL_A;
    2557                 :            : 
    2558                 :         15 :             sh = pl_dispatch_begin(rr->dp);
    2559                 :         15 :             dispatch_sampler(pass, sh, &rr->samplers_dst[p], SAMPLER_PLANE,
    2560                 :         15 :                              plane->texture, &src);
    2561                 :            : 
    2562                 :            :         } else {
    2563                 :            : 
    2564                 :            :             // Single plane, so we can directly re-use the img shader unless
    2565                 :            :             // it's incompatible with the FBO capabilities
    2566                 :       1072 :             bool is_comp = pl_shader_is_compute(img_sh(pass, img));
    2567   [ +  +  -  + ]:       1072 :             if (is_comp && !plane->texture->params.storable) {
    2568         [ #  # ]:          0 :                 if (!img_tex(pass, img)) {
    2569                 :          0 :                     PL_ERR(rr, "Rendering requires compute shaders, but output "
    2570                 :            :                            "is not storable, and FBOs are unavailable. This "
    2571                 :            :                            "combination is unsupported.");
    2572                 :          0 :                     return false;
    2573                 :            :                 }
    2574                 :            :             }
    2575                 :            : 
    2576                 :       1072 :             sh = img_sh(pass, img);
    2577                 :       1072 :             img->sh = NULL;
    2578                 :            : 
    2579                 :            :         }
    2580                 :            : 
    2581                 :            :         // Ignore dithering for > 16-bit outputs by default, since it makes
    2582                 :            :         // little sense to do so (and probably just adds errors)
    2583                 :       1087 :         int depth = target->repr.bits.color_depth, applied_dither = 0;
    2584   [ -  +  +  +  :       1087 :         if (depth && (depth < 16 || params->force_dither)) {
                   +  + ]
    2585         [ -  + ]:        142 :             if (pass_error_diffusion(pass, &sh, depth, plane->components,
    2586                 :            :                                      rx1 - rx0, ry1 - ry0))
    2587                 :            :             {
    2588                 :            :                 applied_dither = depth;
    2589         [ -  + ]:        142 :             } else if (params->dither_params) {
    2590                 :        142 :                 struct pl_dither_params dparams = *params->dither_params;
    2591         [ +  - ]:        142 :                 if (!params->disable_dither_gamma_correction)
    2592                 :        142 :                     dparams.transfer = target->color.transfer;
    2593                 :        142 :                 pl_shader_dither(sh, depth, &rr->dither_state, &dparams);
    2594                 :            :                 applied_dither = depth;
    2595                 :            :             }
    2596                 :            :         }
    2597                 :            : 
    2598         [ +  + ]:       1087 :         if (applied_dither != rr->prev_dither) {
    2599         [ +  + ]:         16 :             if (applied_dither) {
    2600                 :         12 :                 PL_INFO(rr, "Dithering to %d bit depth", applied_dither);
    2601                 :            :             } else {
    2602                 :          4 :                 PL_INFO(rr, "Dithering disabled");
    2603                 :            :             }
    2604                 :         16 :             rr->prev_dither = applied_dither;
    2605                 :            :         }
    2606                 :            : 
    2607   [ +  -  +  - ]:       3261 :         GLSL("color.%s *= vec%d(1.0 / "$"); \n",
    2608                 :            :              params->blend_params ? "rgb" : "rgba",
    2609                 :            :              params->blend_params ? 3 : 4,
    2610                 :            :              SH_FLOAT(scale));
    2611                 :            : 
    2612                 :       1087 :         swizzle_color(sh, plane->components, plane->component_mapping,
    2613                 :       1087 :                       params->blend_params);
    2614                 :            : 
    2615                 :            :         pl_rect2d plane_rect = {
    2616         [ +  + ]:       1087 :             .x0 = flipped_x ? rx1 : rx0,
    2617         [ +  + ]:       1087 :             .x1 = flipped_x ? rx0 : rx1,
    2618         [ +  + ]:       1087 :             .y0 = flipped_y ? ry1 : ry0,
    2619         [ +  + ]:       1087 :             .y1 = flipped_y ? ry0 : ry1,
    2620                 :            :         };
    2621                 :            : 
    2622                 :       1087 :         pl_transform2x2 tscale = {
    2623                 :            :             .mat = {{{ rrx, 0.0 }, { 0.0, rry }}},
    2624                 :       1087 :             .c = { -sx, -sy },
    2625                 :            :         };
    2626                 :            : 
    2627         [ -  + ]:       1087 :         if (plane->flipped) {
    2628                 :          0 :             int plane_h = rry * ref->texture->params.h;
    2629                 :          0 :             plane_rect.y0 = plane_h - plane_rect.y0;
    2630                 :          0 :             plane_rect.y1 = plane_h - plane_rect.y1;
    2631                 :          0 :             tscale.mat.m[1][1] = -tscale.mat.m[1][1];
    2632                 :          0 :             tscale.c[1] += plane->texture->params.h;
    2633                 :            :         }
    2634                 :            : 
    2635                 :       1087 :         bool ok = pl_dispatch_finish(rr->dp, pl_dispatch_params(
    2636                 :            :             .shader = &sh,
    2637                 :            :             .target = plane->texture,
    2638                 :            :             .blend_params = params->blend_params,
    2639                 :            :             .rect = plane_rect,
    2640                 :            :         ));
    2641                 :            : 
    2642         [ +  - ]:       1087 :         if (!ok)
    2643                 :            :             return false;
    2644                 :            : 
    2645         [ +  + ]:       1087 :         if (pass->info.stage != PL_RENDER_STAGE_BLEND) {
    2646                 :        487 :             draw_overlays(pass, plane->texture, plane->components,
    2647                 :        487 :                           plane->component_mapping, image->overlays,
    2648                 :        487 :                           image->num_overlays, target->color, target->repr,
    2649                 :            :                           &tscale);
    2650                 :            :         }
    2651                 :            : 
    2652                 :       1087 :         draw_overlays(pass, plane->texture, plane->components,
    2653                 :       1087 :                       plane->component_mapping, target->overlays,
    2654                 :       1087 :                       target->num_overlays, target->color, target->repr,
    2655                 :            :                       &tscale);
    2656                 :            :     }
    2657                 :            : 
    2658                 :       1077 :     *img = (struct img) {0};
    2659                 :       1077 :     return true;
    2660                 :            : }
    2661                 :            : 
    2662                 :            : #define require(expr) pl_require(rr, expr)
    2663                 :            : #define validate_plane(plane, param)                                            \
    2664                 :            :   do {                                                                          \
    2665                 :            :       require((plane).texture);                                                 \
    2666                 :            :       require((plane).texture->params.param);                                   \
    2667                 :            :       require((plane).components > 0 && (plane).components <= 4);               \
    2668                 :            :       for (int c = 0; c < (plane).components; c++) {                            \
    2669                 :            :           require((plane).component_mapping[c] >= PL_CHANNEL_NONE &&            \
    2670                 :            :                   (plane).component_mapping[c] <= PL_CHANNEL_A);                \
    2671                 :            :       }                                                                         \
    2672                 :            :   } while (0)
    2673                 :            : 
    2674                 :            : #define validate_overlay(overlay)                                               \
    2675                 :            :   do {                                                                          \
    2676                 :            :       require((overlay).tex);                                                   \
    2677                 :            :       require((overlay).tex->params.sampleable);                                \
    2678                 :            :       require((overlay).num_parts >= 0);                                        \
    2679                 :            :       for (int n = 0; n < (overlay).num_parts; n++) {                           \
    2680                 :            :           const struct pl_overlay_part *p = &(overlay).parts[n];                \
    2681                 :            :           require(pl_rect_w(p->dst) && pl_rect_h(p->dst));                      \
    2682                 :            :       }                                                                         \
    2683                 :            :   } while (0)
    2684                 :            : 
    2685                 :            : #define validate_deinterlace_ref(image, ref)                                    \
    2686                 :            :   do {                                                                          \
    2687                 :            :       require((image)->num_planes == (ref)->num_planes);                        \
    2688                 :            :       const struct pl_tex_params *imgp, *refp;                                  \
    2689                 :            :       for (int p = 0; p < (image)->num_planes; p++) {                           \
    2690                 :            :           validate_plane((ref)->planes[p], sampleable);                         \
    2691                 :            :           imgp = &(image)->planes[p].texture->params;                           \
    2692                 :            :           refp = &(ref)->planes[p].texture->params;                             \
    2693                 :            :           require(imgp->w == refp->w);                                          \
    2694                 :            :           require(imgp->h == refp->h);                                          \
    2695                 :            :           require(imgp->format->num_components == refp->format->num_components);\
    2696                 :            :       }                                                                         \
    2697                 :            :   } while (0)
    2698                 :            : 
    2699                 :            : // Perform some basic validity checks on incoming structs to help catch invalid
    2700                 :            : // API usage. This is not an exhaustive check. In particular, enums are not
    2701                 :            : // bounds checked. This is because most functions accepting enums already
    2702                 :            : // abort() in the default case, and because it's not the intent of this check
    2703                 :            : // to catch all instances of memory corruption - just common logic bugs.
    2704                 :       1409 : static bool validate_structs(pl_renderer rr,
    2705                 :            :                              const struct pl_frame *image,
    2706                 :            :                              const struct pl_frame *target)
    2707                 :            : {
    2708                 :            :     // Rendering to/from a frame with no planes is technically allowed, but so
    2709                 :            :     // pointless that it's more likely to be a user error worth catching.
    2710         [ -  + ]:       1409 :     require(target->num_planes > 0 && target->num_planes <= PL_MAX_PLANES);
    2711         [ +  + ]:       2828 :     for (int i = 0; i < target->num_planes; i++)
    2712   [ -  +  -  +  :       5646 :         validate_plane(target->planes[i], renderable);
          -  +  -  +  +  
                      + ]
    2713         [ -  + ]:       1409 :     require(!pl_rect_w(target->crop) == !pl_rect_h(target->crop));
    2714         [ -  + ]:       1409 :     require(target->num_overlays >= 0);
    2715         [ +  + ]:       1417 :     for (int i = 0; i < target->num_overlays; i++)
    2716   [ -  +  -  +  :         16 :         validate_overlay(target->overlays[i]);
          -  +  +  -  -  
                +  +  + ]
    2717                 :            : 
    2718         [ +  + ]:       1409 :     if (!image)
    2719                 :            :         return true;
    2720                 :            : 
    2721         [ -  + ]:        797 :     require(image->num_planes > 0 && image->num_planes <= PL_MAX_PLANES);
    2722         [ +  + ]:       1604 :     for (int i = 0; i < image->num_planes; i++)
    2723   [ -  +  -  +  :       1614 :         validate_plane(image->planes[i], sampleable);
          -  +  -  +  +  
                      + ]
    2724         [ -  + ]:        797 :     require(!pl_rect_w(image->crop) == !pl_rect_h(image->crop));
    2725         [ -  + ]:        797 :     require(image->num_overlays >= 0);
    2726         [ +  + ]:        805 :     for (int i = 0; i < image->num_overlays; i++)
    2727   [ -  +  -  +  :         24 :         validate_overlay(image->overlays[i]);
          -  +  +  -  -  
                +  +  + ]
    2728                 :            : 
    2729         [ +  + ]:        797 :     if (image->field != PL_FIELD_NONE) {
    2730         [ -  + ]:        160 :         require(image->first_field != PL_FIELD_NONE);
    2731         [ +  + ]:        160 :         if (image->prev)
    2732   [ -  +  -  +  :        456 :             validate_deinterlace_ref(image, image->prev);
          -  +  -  +  -  
          +  +  +  -  +  
          -  +  -  +  +  
                      + ]
    2733         [ +  + ]:        160 :         if (image->next)
    2734   [ -  +  -  +  :        456 :             validate_deinterlace_ref(image, image->next);
          -  +  -  +  -  
          +  +  +  -  +  
          -  +  -  +  +  
                      + ]
    2735                 :            :     }
    2736                 :            : 
    2737                 :            :     return true;
    2738                 :            : 
    2739                 :            : error:
    2740                 :            :     return false;
    2741                 :            : }
    2742                 :            : 
    2743                 :            : // returns index
    2744                 :       6750 : static int frame_ref(const struct pl_frame *frame)
    2745                 :            : {
    2746         [ +  - ]:       6750 :     pl_assert(frame->num_planes);
    2747         [ +  - ]:       6750 :     for (int i = 0; i < frame->num_planes; i++) {
    2748   [ -  -  -  + ]:       6750 :         switch (detect_plane_type(&frame->planes[i], &frame->repr)) {
    2749                 :            :         case PLANE_RGB:
    2750                 :            :         case PLANE_LUMA:
    2751                 :            :         case PLANE_XYZ:
    2752                 :            :             return i;
    2753                 :          0 :         case PLANE_CHROMA:
    2754                 :            :         case PLANE_ALPHA:
    2755                 :          0 :             continue;
    2756                 :            :         case PLANE_INVALID:
    2757                 :          0 :             pl_unreachable();
    2758                 :            :         }
    2759                 :            :     }
    2760                 :            : 
    2761                 :            :     return 0;
    2762                 :            : }
    2763                 :            : 
    2764                 :       1421 : static void fix_refs_and_rects(struct pass_state *pass)
    2765                 :            : {
    2766                 :       1421 :     struct pl_frame *target = &pass->target;
    2767                 :       1421 :     pl_rect2df *dst = &target->crop;
    2768                 :       1421 :     pass->dst_ref = frame_ref(target);
    2769                 :       1421 :     pl_tex dst_ref = target->planes[pass->dst_ref].texture;
    2770                 :       1421 :     int dst_w = dst_ref->params.w, dst_h = dst_ref->params.h;
    2771                 :            : 
    2772   [ +  -  +  +  :       1421 :     if ((!dst->x0 && !dst->x1) || (!dst->y0 && !dst->y1)) {
             +  -  -  + ]
    2773                 :       1309 :         dst->x1 = dst_w;
    2774                 :       1309 :         dst->y1 = dst_h;
    2775                 :            :     }
    2776                 :            : 
    2777         [ +  + ]:       1421 :     if (pass->src_ref < 0) {
    2778                 :            :         // Simplified version of the below code which only rounds the target
    2779                 :            :         // rect but doesn't retroactively apply the crop to the image
    2780                 :          8 :         pass->rotation = pl_rotation_normalize(-target->rotation);
    2781                 :          8 :         pl_rect2df_rotate(dst, -pass->rotation);
    2782         [ -  + ]:          8 :         if (pass->rotation % PL_ROTATION_180 == PL_ROTATION_90)
    2783                 :            :             PL_SWAP(dst_w, dst_h);
    2784                 :            : 
    2785                 :          8 :         *dst = (pl_rect2df) {
    2786   [ +  -  +  - ]:          8 :             .x0 = roundf(PL_CLAMP(dst->x0, 0.0, dst_w)),
    2787   [ +  -  +  - ]:          8 :             .y0 = roundf(PL_CLAMP(dst->y0, 0.0, dst_h)),
    2788   [ +  -  +  - ]:          8 :             .x1 = roundf(PL_CLAMP(dst->x1, 0.0, dst_w)),
    2789   [ +  -  +  - ]:          8 :             .y1 = roundf(PL_CLAMP(dst->y1, 0.0, dst_h)),
    2790                 :            :         };
    2791                 :            : 
    2792                 :          8 :         pass->dst_rect = (pl_rect2d) {
    2793                 :            :             dst->x0, dst->y0, dst->x1, dst->y1,
    2794                 :            :         };
    2795                 :            : 
    2796                 :            :         return;
    2797                 :            :     }
    2798                 :            : 
    2799                 :       1413 :     struct pl_frame *image = &pass->image;
    2800                 :       1413 :     pl_rect2df *src = &image->crop;
    2801                 :       1413 :     pass->src_ref = frame_ref(image);
    2802                 :       1413 :     pl_tex src_ref = image->planes[pass->src_ref].texture;
    2803                 :            : 
    2804   [ +  -  +  +  :       1413 :     if ((!src->x0 && !src->x1) || (!src->y0 && !src->y1)) {
             +  -  -  + ]
    2805                 :       1301 :         src->x1 = src_ref->params.w;
    2806                 :       1301 :         src->y1 = src_ref->params.h;
    2807                 :            :     };
    2808                 :            : 
    2809                 :            :     // Compute end-to-end rotation
    2810                 :       1413 :     pass->rotation = pl_rotation_normalize(image->rotation - target->rotation);
    2811                 :       1413 :     pl_rect2df_rotate(dst, -pass->rotation); // normalize by counter-rotating
    2812         [ +  + ]:       1413 :     if (pass->rotation % PL_ROTATION_180 == PL_ROTATION_90)
    2813                 :            :         PL_SWAP(dst_w, dst_h);
    2814                 :            : 
    2815                 :            :     // Keep track of whether the end-to-end rendering is flipped
    2816                 :       1413 :     bool flipped_x = (src->x0 > src->x1) != (dst->x0 > dst->x1),
    2817                 :       1413 :          flipped_y = (src->y0 > src->y1) != (dst->y0 > dst->y1);
    2818                 :            : 
    2819                 :            :     // Normalize both rects to make the math easier
    2820                 :       1413 :     pl_rect2df_normalize(src);
    2821                 :       1413 :     pl_rect2df_normalize(dst);
    2822                 :            : 
    2823                 :            :     // Round the output rect and clip it to the framebuffer dimensions
    2824   [ +  -  +  - ]:       1413 :     float rx0 = roundf(PL_CLAMP(dst->x0, 0.0, dst_w)),
    2825   [ +  -  +  - ]:       1413 :           ry0 = roundf(PL_CLAMP(dst->y0, 0.0, dst_h)),
    2826   [ +  -  +  - ]:       1413 :           rx1 = roundf(PL_CLAMP(dst->x1, 0.0, dst_w)),
    2827   [ +  -  +  - ]:       1413 :           ry1 = roundf(PL_CLAMP(dst->y1, 0.0, dst_h));
    2828                 :            : 
    2829                 :            :     // Adjust the src rect corresponding to the rounded crop
    2830                 :       1413 :     float scale_x = pl_rect_w(*src) / pl_rect_w(*dst),
    2831                 :       1413 :           scale_y = pl_rect_h(*src) / pl_rect_h(*dst),
    2832                 :            :           base_x = src->x0,
    2833                 :            :           base_y = src->y0;
    2834                 :            : 
    2835                 :       1413 :     src->x0 = base_x + (rx0 - dst->x0) * scale_x;
    2836                 :       1413 :     src->x1 = base_x + (rx1 - dst->x0) * scale_x;
    2837                 :       1413 :     src->y0 = base_y + (ry0 - dst->y0) * scale_y;
    2838                 :       1413 :     src->y1 = base_y + (ry1 - dst->y0) * scale_y;
    2839                 :            : 
    2840                 :            :     // Update dst_rect to the rounded values and re-apply flip if needed. We
    2841                 :            :     // always do this in the `dst` rather than the `src`` because this allows
    2842                 :            :     // e.g. polar sampling compute shaders to work.
    2843                 :       1413 :     *dst = (pl_rect2df) {
    2844         [ +  + ]:       1413 :         .x0 = flipped_x ? rx1 : rx0,
    2845         [ +  + ]:       1413 :         .y0 = flipped_y ? ry1 : ry0,
    2846         [ +  + ]:       1413 :         .x1 = flipped_x ? rx0 : rx1,
    2847         [ +  + ]:       1413 :         .y1 = flipped_y ? ry0 : ry1,
    2848                 :            :     };
    2849                 :            : 
    2850                 :            :     // Copies of the above, for convenience
    2851                 :       1413 :     pass->ref_rect = *src;
    2852                 :       1413 :     pass->dst_rect = (pl_rect2d) {
    2853                 :            :         dst->x0, dst->y0, dst->x1, dst->y1,
    2854                 :            :     };
    2855                 :            : }
    2856                 :            : 
    2857                 :       2834 : static void fix_frame(struct pl_frame *frame)
    2858                 :            : {
    2859                 :       2834 :     pl_tex tex = frame->planes[frame_ref(frame)].texture;
    2860                 :            : 
    2861         [ -  + ]:       2834 :     if (frame->repr.sys == PL_COLOR_SYSTEM_XYZ) {
    2862                 :            :         // XYZ is implicity converted to linear DCI-P3 in pl_color_repr_decode
    2863                 :          0 :         frame->color.primaries = PL_COLOR_PRIM_DCI_P3;
    2864                 :          0 :         frame->color.transfer = PL_COLOR_TRC_ST428;
    2865                 :            :     }
    2866                 :            : 
    2867                 :            :     // If the primaries are not known, guess them based on the resolution
    2868   [ +  -  -  + ]:       2834 :     if (tex && !frame->color.primaries)
    2869                 :          0 :         frame->color.primaries = pl_color_primaries_guess(tex->params.w, tex->params.h);
    2870                 :            : 
    2871                 :            :     // For UNORM formats, we can infer the sampled bit depth from the texture
    2872                 :            :     // itself. This is ignored for other format types, because the logic
    2873                 :            :     // doesn't really work out for them anyways, and it's best not to do
    2874                 :            :     // anything too crazy unless the user provides explicit details.
    2875                 :            :     struct pl_bit_encoding *bits = &frame->repr.bits;
    2876   [ +  -  +  -  :       2834 :     if (!bits->sample_depth && tex && tex->params.format->type == PL_FMT_UNORM) {
                   +  + ]
    2877                 :            :         // Just assume the first component's depth is canonical. This works in
    2878                 :            :         // practice, since for cases like rgb565 we want to use the lower depth
    2879                 :            :         // anyway. Plus, every format has at least one component.
    2880                 :         10 :         bits->sample_depth = tex->params.format->component_depth[0];
    2881                 :            : 
    2882                 :            :         // If we don't know the color depth, assume it spans the full range of
    2883                 :            :         // the texture. Otherwise, clamp it to the texture depth.
    2884         [ +  - ]:         10 :         bits->color_depth = PL_DEF(bits->color_depth, bits->sample_depth);
    2885                 :         10 :         bits->color_depth = PL_MIN(bits->color_depth, bits->sample_depth);
    2886                 :            : 
    2887                 :            :         // If the texture depth is higher than the known color depth, assume
    2888                 :            :         // the colors were left-shifted.
    2889                 :         10 :         bits->bit_shift += bits->sample_depth - bits->color_depth;
    2890                 :            :     }
    2891                 :       2834 : }
    2892                 :            : 
    2893                 :            : static bool acquire_frame(struct pass_state *pass, struct pl_frame *frame,
    2894                 :            :                           bool *acquired)
    2895                 :            : {
    2896   [ -  +  -  -  :       2206 :     if (!frame || !frame->acquire || *acquired)
          -  +  -  -  -  
                -  -  - ]
    2897                 :            :         return true;
    2898                 :            : 
    2899                 :          0 :     *acquired = true;
    2900                 :          0 :     return frame->acquire(pass->rr->gpu, frame);
    2901                 :            : }
    2902                 :            : 
    2903                 :            : static void release_frame(struct pass_state *pass, struct pl_frame *frame,
    2904                 :            :                           bool *acquired)
    2905                 :            : {
    2906   [ -  -  -  +  :       4227 :     if (frame && frame->release && *acquired)
          -  -  -  +  -  
             -  -  +  -  
                      - ]
    2907                 :          0 :         frame->release(pass->rr->gpu, frame);
    2908                 :       5636 :     *acquired = false;
    2909                 :            : }
    2910                 :            : 
    2911                 :       1409 : static void pass_uninit(struct pass_state *pass)
    2912                 :            : {
    2913                 :       1409 :     pl_renderer rr = pass->rr;
    2914                 :       1409 :     pl_dispatch_abort(rr->dp, &pass->img.sh);
    2915         [ +  - ]:       1409 :     release_frame(pass, &pass->next, &pass->acquired.next);
    2916                 :       1409 :     release_frame(pass, &pass->prev, &pass->acquired.prev);
    2917                 :       1409 :     release_frame(pass, &pass->image, &pass->acquired.image);
    2918                 :       1409 :     release_frame(pass, &pass->target, &pass->acquired.target);
    2919                 :       1409 :     pl_free_ptr(&pass->tmp);
    2920                 :       1409 : }
    2921                 :            : 
    2922                 :       2842 : static void icc_fallback(struct pass_state *pass, struct pl_frame *frame,
    2923                 :            :                          struct icc_state *fallback)
    2924                 :            : {
    2925   [ +  +  +  -  :       2842 :     if (!frame || frame->icc || !frame->profile.data)
                   +  + ]
    2926                 :            :         return;
    2927                 :            : 
    2928                 :            :     // Don't re-attempt opening already failed profiles
    2929   [ -  +  -  - ]:         16 :     if (fallback->error && fallback->error == frame->profile.signature)
    2930                 :            :         return;
    2931                 :            : 
    2932                 :            : #ifdef PL_HAVE_LCMS
    2933                 :         16 :     pl_renderer rr = pass->rr;
    2934         [ +  - ]:         16 :     if (pl_icc_update(rr->log, &fallback->icc, &frame->profile, NULL)) {
    2935                 :         16 :         frame->icc = fallback->icc;
    2936                 :            :     } else {
    2937                 :          0 :         PL_WARN(rr, "Failed opening ICC profile... ignoring");
    2938                 :          0 :         fallback->error = frame->profile.signature;
    2939                 :            :     }
    2940                 :            : #endif
    2941                 :            : }
    2942                 :            : 
    2943                 :       1421 : static void pass_fix_frames(struct pass_state *pass)
    2944                 :            : {
    2945                 :       1421 :     pl_renderer rr = pass->rr;
    2946         [ +  + ]:       1421 :     struct pl_frame *image = pass->src_ref < 0 ? NULL : &pass->image;
    2947                 :       1421 :     struct pl_frame *target = &pass->target;
    2948                 :            : 
    2949                 :       1421 :     fix_refs_and_rects(pass);
    2950                 :            : 
    2951                 :            :     // Fallback for older ICC profile API
    2952                 :       1421 :     icc_fallback(pass, image,  &rr->icc_fallback[ICC_IMAGE]);
    2953                 :       1421 :     icc_fallback(pass, target, &rr->icc_fallback[ICC_TARGET]);
    2954                 :            : 
    2955                 :            :     // Force colorspace metadata to ICC profile values, if present
    2956   [ +  +  +  + ]:       1421 :     if (image && image->icc) {
    2957                 :          8 :         image->color.primaries = image->icc->containing_primaries;
    2958                 :          8 :         image->color.hdr = image->icc->csp.hdr;
    2959         [ +  - ]:          8 :         if (image->icc->csp.transfer)
    2960                 :          8 :             image->color.transfer = image->icc->csp.transfer;
    2961                 :            :     }
    2962                 :            : 
    2963         [ +  + ]:       1421 :     if (target->icc) {
    2964                 :          8 :         target->color.primaries = target->icc->containing_primaries;
    2965                 :          8 :         target->color.hdr = target->icc->csp.hdr;
    2966         [ +  - ]:          8 :         if (target->icc->csp.transfer)
    2967                 :          8 :             target->color.transfer = target->icc->csp.transfer;
    2968                 :            :     }
    2969                 :            : 
    2970                 :            :     // Infer the target color space info based on the image's
    2971         [ +  + ]:       1421 :     if (image) {
    2972                 :       1413 :         fix_frame(image);
    2973                 :       1413 :         pl_color_space_infer_map(&image->color, &target->color);
    2974                 :       1413 :         fix_frame(target); // do this only after infer_map
    2975                 :            :     } else {
    2976                 :          8 :         fix_frame(target);
    2977                 :          8 :         pl_color_space_infer(&target->color);
    2978                 :            :     }
    2979                 :            : 
    2980                 :            :     // Detect the presence of an alpha channel in the frames and explicitly
    2981                 :            :     // default the alpha mode in this case, so we can use it to detect whether
    2982                 :            :     // or not to strip the alpha channel during rendering.
    2983                 :            :     //
    2984                 :            :     // Note the different defaults for the image and target, because files
    2985                 :            :     // are usually independent but windowing systems usually expect
    2986                 :            :     // premultiplied. (We also premultiply for internal rendering, so this
    2987                 :            :     // way of doing it avoids a possible division-by-zero path!)
    2988   [ +  +  +  + ]:       1421 :     if (image && !image->repr.alpha) {
    2989                 :       1409 :         image->repr.alpha = PL_ALPHA_NONE;
    2990         [ +  + ]:       2828 :         for (int i = 0; i < image->num_planes; i++) {
    2991                 :            :             const struct pl_plane *plane = &image->planes[i];
    2992         [ +  + ]:       2838 :             for (int c = 0; c < plane->components; c++) {
    2993         [ -  + ]:       1419 :                 if (plane->component_mapping[c] == PL_CHANNEL_A)
    2994                 :          0 :                     image->repr.alpha = PL_ALPHA_INDEPENDENT;
    2995                 :            :             }
    2996                 :            :         }
    2997                 :            :     }
    2998                 :            : 
    2999         [ +  + ]:       1421 :     if (!target->repr.alpha) {
    3000                 :       1417 :         target->repr.alpha = PL_ALPHA_NONE;
    3001         [ +  + ]:       2844 :         for (int i = 0; i < target->num_planes; i++) {
    3002                 :            :             const struct pl_plane *plane = &target->planes[i];
    3003         [ +  + ]:       5678 :             for (int c = 0; c < plane->components; c++) {
    3004         [ -  + ]:       4251 :                 if (plane->component_mapping[c] == PL_CHANNEL_A)
    3005                 :          0 :                     target->repr.alpha = PL_ALPHA_PREMULTIPLIED;
    3006                 :            :             }
    3007                 :            :         }
    3008                 :            :     }
    3009                 :       1421 : }
    3010                 :            : 
    3011                 :          4 : void pl_frames_infer(pl_renderer rr, struct pl_frame *image,
    3012                 :            :                      struct pl_frame *target)
    3013                 :            : {
    3014                 :          4 :     struct pass_state pass = {
    3015                 :            :         .rr     = rr,
    3016                 :            :         .image  = *image,
    3017                 :            :         .target = *target,
    3018                 :            :     };
    3019                 :            : 
    3020                 :          4 :     pass_fix_frames(&pass);
    3021                 :          4 :     *image  = pass.image;
    3022                 :          4 :     *target = pass.target;
    3023                 :          4 : }
    3024                 :            : 
    3025                 :       1409 : static bool pass_init(struct pass_state *pass, bool acquire_image)
    3026                 :            : {
    3027         [ +  + ]:       1409 :     struct pl_frame *image = pass->src_ref < 0 ? NULL : &pass->image;
    3028                 :       1409 :     struct pl_frame *target = &pass->target;
    3029                 :            : 
    3030         [ #  # ]:          0 :     if (!acquire_frame(pass, target, &pass->acquired.target))
    3031                 :          0 :         goto error;
    3032         [ +  + ]:       1409 :     if (acquire_image && image) {
    3033         [ #  # ]:          0 :         if (!acquire_frame(pass, image, &pass->acquired.image))
    3034                 :          0 :             goto error;
    3035                 :            : 
    3036                 :        797 :         const struct pl_render_params *params = pass->params;
    3037                 :        797 :         const struct pl_deinterlace_params *deint = params->deinterlace_params;
    3038   [ +  +  +  - ]:        797 :         bool needs_refs = image->field != PL_FIELD_NONE && deint &&
    3039         [ #  # ]:          0 :                           pl_deinterlace_needs_refs(deint->algo);
    3040                 :            : 
    3041   [ +  +  -  + ]:        797 :         if (image->prev && needs_refs) {
    3042                 :            :             // Move into local copy so we can acquire/release it
    3043                 :          0 :             pass->prev = *image->prev;
    3044         [ #  # ]:          0 :             image->prev = &pass->prev;
    3045         [ #  # ]:          0 :             if (!acquire_frame(pass, &pass->prev, &pass->acquired.prev))
    3046                 :          0 :                 goto error;
    3047                 :            :         }
    3048   [ +  +  -  + ]:        797 :         if (image->next && needs_refs) {
    3049                 :          0 :             pass->next = *image->next;
    3050         [ #  # ]:          0 :             image->next = &pass->next;
    3051         [ #  # ]:          0 :             if (!acquire_frame(pass, &pass->next, &pass->acquired.next))
    3052                 :          0 :                 goto error;
    3053                 :            :         }
    3054                 :            :     }
    3055                 :            : 
    3056   [ +  +  -  + ]:       2021 :     if (!validate_structs(pass->rr, acquire_image ? image : NULL, target))
    3057                 :          0 :         goto error;
    3058                 :            : 
    3059                 :       1409 :     find_fbo_format(pass);
    3060                 :       1409 :     pass_fix_frames(pass);
    3061                 :            : 
    3062                 :       1409 :     pass->tmp = pl_tmp(NULL);
    3063                 :       1409 :     return true;
    3064                 :            : 
    3065                 :          0 : error:
    3066                 :          0 :     pass_uninit(pass);
    3067                 :          0 :     return false;
    3068                 :            : }
    3069                 :            : 
    3070                 :       1401 : static void pass_begin_frame(struct pass_state *pass)
    3071                 :            : {
    3072                 :       1401 :     pl_renderer rr = pass->rr;
    3073                 :       1401 :     const struct pl_render_params *params = pass->params;
    3074                 :            : 
    3075                 :       1401 :     pl_dispatch_callback(rr->dp, pass, info_callback);
    3076                 :       1401 :     pl_dispatch_reset_frame(rr->dp);
    3077                 :            : 
    3078         [ +  + ]:       1416 :     for (int i = 0; i < params->num_hooks; i++) {
    3079         [ +  + ]:         15 :         if (params->hooks[i]->reset)
    3080                 :         10 :             params->hooks[i]->reset(params->hooks[i]->priv);
    3081                 :            :     }
    3082                 :            : 
    3083                 :       1401 :     size_t size = rr->fbos.num * sizeof(bool);
    3084                 :       1401 :     pass->fbos_used = pl_realloc(pass->tmp, pass->fbos_used, size);
    3085                 :            :     memset(pass->fbos_used, 0, size);
    3086                 :       1401 : }
    3087                 :            : 
    3088                 :         12 : static bool draw_empty_overlays(pl_renderer rr,
    3089                 :            :                                 const struct pl_frame *ptarget,
    3090                 :            :                                 const struct pl_render_params *params)
    3091                 :            : {
    3092                 :         12 :     clear_target(rr, ptarget, params);
    3093         [ +  + ]:         12 :     if (!ptarget->num_overlays)
    3094                 :            :         return true;
    3095                 :            : 
    3096                 :          4 :     struct pass_state pass = {
    3097                 :            :         .rr = rr,
    3098                 :            :         .params = params,
    3099                 :            :         .src_ref = -1,
    3100                 :            :         .target = *ptarget,
    3101                 :            :         .info.stage = PL_RENDER_STAGE_BLEND,
    3102                 :            :         .info.count = 0,
    3103                 :            :     };
    3104                 :            : 
    3105         [ +  - ]:          4 :     if (!pass_init(&pass, false))
    3106                 :            :         return false;
    3107                 :            : 
    3108                 :          4 :     pass_begin_frame(&pass);
    3109                 :            :     struct pl_frame *target = &pass.target;
    3110                 :          4 :     pl_tex ref = target->planes[pass.dst_ref].texture;
    3111         [ +  + ]:          8 :     for (int p = 0; p < target->num_planes; p++) {
    3112                 :            :         const struct pl_plane *plane = &target->planes[p];
    3113                 :            :         // Math replicated from `pass_output_target`
    3114                 :          4 :         float rx = (float) plane->texture->params.w / ref->params.w,
    3115                 :          4 :               ry = (float) plane->texture->params.h / ref->params.h;
    3116         [ +  - ]:          4 :         float rrx = rx >= 1 ? roundf(rx) : 1.0 / roundf(1.0 / rx),
    3117         [ +  - ]:          4 :               rry = ry >= 1 ? roundf(ry) : 1.0 / roundf(1.0 / ry);
    3118                 :          4 :         float sx = plane->shift_x, sy = plane->shift_y;
    3119                 :            : 
    3120                 :          4 :         pl_transform2x2 tscale = {
    3121                 :            :             .mat = {{{ rrx, 0.0 }, { 0.0, rry }}},
    3122                 :          4 :             .c = { -sx, -sy },
    3123                 :            :         };
    3124                 :            : 
    3125         [ -  + ]:          4 :         if (plane->flipped) {
    3126                 :          0 :             tscale.mat.m[1][1] = -tscale.mat.m[1][1];
    3127                 :          0 :             tscale.c[1] += plane->texture->params.h;
    3128                 :            :         }
    3129                 :            : 
    3130                 :          4 :         draw_overlays(&pass, plane->texture, plane->components,
    3131                 :          4 :                       plane->component_mapping, target->overlays,
    3132                 :            :                       target->num_overlays, target->color, target->repr,
    3133                 :            :                       &tscale);
    3134                 :            :     }
    3135                 :            : 
    3136                 :          4 :     pass_uninit(&pass);
    3137                 :          4 :     return true;
    3138                 :            : }
    3139                 :            : 
    3140                 :        489 : bool pl_render_image(pl_renderer rr, const struct pl_frame *pimage,
    3141                 :            :                      const struct pl_frame *ptarget,
    3142                 :            :                      const struct pl_render_params *params)
    3143                 :            : {
    3144         [ +  + ]:        489 :     params = PL_DEF(params, &pl_render_default_params);
    3145                 :        489 :     pl_dispatch_mark_dynamic(rr->dp, params->dynamic_constants);
    3146         [ +  + ]:        489 :     if (!pimage)
    3147                 :         12 :         return draw_empty_overlays(rr, ptarget, params);
    3148                 :            : 
    3149                 :        477 :     struct pass_state pass = {
    3150                 :            :         .rr = rr,
    3151                 :            :         .params = params,
    3152                 :            :         .image = *pimage,
    3153                 :            :         .target = *ptarget,
    3154                 :            :         .info.stage = PL_RENDER_STAGE_FRAME,
    3155                 :            :     };
    3156                 :            : 
    3157         [ -  + ]:        477 :     if (!pass_init(&pass, true))
    3158                 :            :         return false;
    3159                 :            : 
    3160                 :            :     // No-op (empty crop)
    3161   [ +  -  -  + ]:        477 :     if (!pl_rect_w(pass.dst_rect) || !pl_rect_h(pass.dst_rect)) {
    3162                 :          0 :         pass_uninit(&pass);
    3163                 :          0 :         return draw_empty_overlays(rr, ptarget, params);
    3164                 :            :     }
    3165                 :            : 
    3166                 :        477 :     pass_begin_frame(&pass);
    3167         [ -  + ]:        477 :     if (!pass_read_image(&pass))
    3168                 :          0 :         goto error;
    3169         [ -  + ]:        477 :     if (!pass_scale_main(&pass))
    3170                 :          0 :         goto error;
    3171                 :        477 :     pass_convert_colors(&pass);
    3172         [ -  + ]:        477 :     if (!pass_output_target(&pass))
    3173                 :          0 :         goto error;
    3174                 :            : 
    3175                 :        477 :     pass_uninit(&pass);
    3176                 :        477 :     return true;
    3177                 :            : 
    3178                 :          0 : error:
    3179                 :          0 :     PL_ERR(rr, "Failed rendering image!");
    3180                 :          0 :     pass_uninit(&pass);
    3181                 :          0 :     return false;
    3182                 :            : }
    3183                 :            : 
    3184                 :          0 : const struct pl_frame *pl_frame_mix_current(const struct pl_frame_mix *mix)
    3185                 :            : {
    3186                 :            :     const struct pl_frame *cur = NULL;
    3187         [ #  # ]:          0 :     for (int i = 0; i < mix->num_frames; i++) {
    3188         [ #  # ]:          0 :         if (mix->timestamps[i] > 0.0f)
    3189                 :            :             break;
    3190                 :          0 :         cur = mix->frames[i];
    3191                 :            :     }
    3192                 :            : 
    3193                 :          0 :     return cur;
    3194                 :            : }
    3195                 :            : 
    3196                 :        616 : const struct pl_frame *pl_frame_mix_nearest(const struct pl_frame_mix *mix)
    3197                 :            : {
    3198         [ +  + ]:        616 :     if (!mix->num_frames)
    3199                 :            :         return NULL;
    3200                 :            : 
    3201                 :        612 :     const struct pl_frame *best = mix->frames[0];
    3202                 :        612 :     float best_dist = fabsf(mix->timestamps[0]);
    3203         [ +  + ]:       1224 :     for (int i = 1; i < mix->num_frames; i++) {
    3204                 :       1036 :         float dist = fabsf(mix->timestamps[i]);
    3205         [ +  + ]:       1036 :         if (dist < best_dist) {
    3206                 :        612 :             best = mix->frames[i];
    3207                 :            :             best_dist = dist;
    3208                 :            :             continue;
    3209                 :            :         } else {
    3210                 :            :             break;
    3211                 :            :         }
    3212                 :            :     }
    3213                 :            : 
    3214                 :            :     return best;
    3215                 :            : }
    3216                 :            : 
    3217                 :            : struct params_info {
    3218                 :            :     uint64_t hash;
    3219                 :            :     bool trivial;
    3220                 :            : };
    3221                 :            : 
    3222                 :        608 : static struct params_info render_params_info(const struct pl_render_params *params_orig)
    3223                 :            : {
    3224                 :        608 :     struct pl_render_params params = *params_orig;
    3225                 :            :     struct params_info info = {
    3226                 :            :         .trivial = true,
    3227                 :            :         .hash = 0,
    3228                 :            :     };
    3229                 :            : 
    3230                 :            : #define HASH_PTR(ptr, def, ptr_trivial)                                         \
    3231                 :            :     do {                                                                        \
    3232                 :            :         if (ptr) {                                                              \
    3233                 :            :             pl_hash_merge(&info.hash, pl_mem_hash(ptr, sizeof(*ptr)));          \
    3234                 :            :             info.trivial &= (ptr_trivial);                                      \
    3235                 :            :             ptr = NULL;                                                         \
    3236                 :            :         } else if ((def) != NULL) {                                             \
    3237                 :            :             pl_hash_merge(&info.hash, pl_mem_hash(def, sizeof(*ptr)));          \
    3238                 :            :         }                                                                       \
    3239                 :            :     } while (0)
    3240                 :            : 
    3241                 :            : #define HASH_FILTER(scaler)                                                     \
    3242                 :            :     do {                                                                        \
    3243                 :            :         if ((scaler == &pl_filter_bilinear || scaler == &pl_filter_nearest) &&  \
    3244                 :            :             params.skip_anti_aliasing)                                          \
    3245                 :            :         {                                                                       \
    3246                 :            :             /* treat as NULL */                                                 \
    3247                 :            :         } else if (scaler) {                                                    \
    3248                 :            :             struct pl_filter_config filter = *scaler;                           \
    3249                 :            :             HASH_PTR(filter.kernel, NULL, false);                               \
    3250                 :            :             HASH_PTR(filter.window, NULL, false);                               \
    3251                 :            :             pl_hash_merge(&info.hash, pl_var_hash(filter));                     \
    3252                 :            :             scaler = NULL;                                                      \
    3253                 :            :         }                                                                       \
    3254                 :            :     } while (0)
    3255                 :            : 
    3256   [ +  -  -  +  :        608 :     HASH_FILTER(params.upscaler);
          -  -  -  +  -  
                -  -  - ]
    3257   [ +  -  -  +  :        608 :     HASH_FILTER(params.downscaler);
          -  -  -  +  -  
                -  -  - ]
    3258                 :            : 
    3259         [ -  + ]:        608 :     HASH_PTR(params.deband_params, NULL, false);
    3260         [ -  + ]:        608 :     HASH_PTR(params.sigmoid_params, NULL, false);
    3261         [ -  + ]:        608 :     HASH_PTR(params.deinterlace_params, NULL, false);
    3262         [ -  + ]:        608 :     HASH_PTR(params.cone_params, NULL, true);
    3263         [ -  + ]:        608 :     HASH_PTR(params.icc_params, &pl_icc_default_params, true);
    3264         [ -  + ]:        608 :     HASH_PTR(params.color_adjustment, &pl_color_adjustment_neutral, true);
    3265         [ -  + ]:        608 :     HASH_PTR(params.color_map_params, &pl_color_map_default_params, true);
    3266         [ -  + ]:        608 :     HASH_PTR(params.peak_detect_params, NULL, false);
    3267                 :            : 
    3268                 :            :     // Hash all hooks
    3269         [ -  + ]:        608 :     for (int i = 0; i < params.num_hooks; i++) {
    3270                 :          0 :         const struct pl_hook *hook = params.hooks[i];
    3271         [ #  # ]:          0 :         if (hook->stages == PL_HOOK_OUTPUT)
    3272                 :          0 :             continue; // ignore hooks only relevant to pass_output_target
    3273                 :            :         pl_hash_merge(&info.hash, pl_var_hash(*hook));
    3274                 :            :         info.trivial = false;
    3275                 :            :     }
    3276                 :        608 :     params.hooks = NULL;
    3277                 :            : 
    3278                 :            :     // Hash the LUT by only looking at the signature
    3279         [ -  + ]:        608 :     if (params.lut) {
    3280                 :          0 :         pl_hash_merge(&info.hash, params.lut->signature);
    3281                 :            :         info.trivial = false;
    3282                 :          0 :         params.lut = NULL;
    3283                 :            :     }
    3284                 :            : 
    3285                 :            : #define CLEAR(field) field = (__typeof__(field)) {0}
    3286                 :            : 
    3287                 :            :     // Clear out fields only relevant to pl_render_image_mix
    3288                 :        608 :     CLEAR(params.frame_mixer);
    3289                 :        608 :     CLEAR(params.preserve_mixing_cache);
    3290                 :        608 :     CLEAR(params.skip_caching_single_frame);
    3291                 :            : 
    3292                 :            :     // Clear out fields only relevant to pass_output_target
    3293                 :        608 :     CLEAR(params.background);
    3294                 :        608 :     CLEAR(params.border);
    3295                 :        608 :     CLEAR(params.skip_target_clearing);
    3296                 :        608 :     CLEAR(params.blend_against_tiles);
    3297                 :            :     memset(params.background_color, 0, sizeof(params.background_color));
    3298                 :        608 :     CLEAR(params.background_transparency);
    3299                 :            :     memset(params.tile_colors, 0, sizeof(params.tile_colors));
    3300                 :        608 :     CLEAR(params.tile_size);
    3301                 :        608 :     CLEAR(params.blend_params);
    3302                 :        608 :     CLEAR(params.distort_params);
    3303                 :        608 :     CLEAR(params.dither_params);
    3304                 :        608 :     CLEAR(params.error_diffusion);
    3305                 :        608 :     CLEAR(params.force_dither);
    3306                 :        608 :     CLEAR(params.corner_rounding);
    3307                 :            : 
    3308                 :            :     // Clear out other irrelevant fields
    3309                 :        608 :     CLEAR(params.dynamic_constants);
    3310                 :        608 :     CLEAR(params.info_callback);
    3311                 :        608 :     CLEAR(params.info_priv);
    3312                 :            : 
    3313                 :            :     pl_hash_merge(&info.hash, pl_var_hash(params));
    3314                 :        608 :     return info;
    3315                 :            : }
    3316                 :            : 
    3317                 :            : #define MAX_MIX_FRAMES 16
    3318                 :            : 
    3319                 :        616 : bool pl_render_image_mix(pl_renderer rr, const struct pl_frame_mix *images,
    3320                 :            :                          const struct pl_frame *ptarget,
    3321                 :            :                          const struct pl_render_params *params)
    3322                 :            : {
    3323         [ +  + ]:        616 :     if (!images->num_frames)
    3324                 :          8 :         return pl_render_image(rr, NULL, ptarget, params);
    3325                 :            : 
    3326         [ -  + ]:        608 :     params = PL_DEF(params, &pl_render_default_params);
    3327                 :        608 :     struct params_info par_info = render_params_info(params);
    3328                 :        608 :     pl_dispatch_mark_dynamic(rr->dp, params->dynamic_constants);
    3329                 :            : 
    3330         [ -  + ]:        608 :     require(images->num_frames >= 1);
    3331         [ -  + ]:        608 :     require(images->vsync_duration > 0.0);
    3332         [ +  + ]:       1744 :     for (int i = 0; i < images->num_frames - 1; i++)
    3333         [ -  + ]:       1136 :         require(images->timestamps[i] <= images->timestamps[i+1]);
    3334                 :            : 
    3335                 :        608 :     const struct pl_frame *refimg = pl_frame_mix_nearest(images);
    3336                 :        608 :     struct pass_state pass = {
    3337                 :            :         .rr = rr,
    3338                 :            :         .params = params,
    3339                 :            :         .image = *refimg,
    3340                 :            :         .target = *ptarget,
    3341                 :            :         .info.stage = PL_RENDER_STAGE_BLEND,
    3342                 :            :     };
    3343                 :            : 
    3344         [ -  + ]:        608 :     if (rr->errors & PL_RENDER_ERR_FRAME_MIXING)
    3345                 :          0 :         goto fallback;
    3346         [ +  - ]:        608 :     if (!pass_init(&pass, false))
    3347                 :            :         return false;
    3348         [ -  + ]:        608 :     if (!pass.fbofmt[4])
    3349                 :          0 :         goto fallback;
    3350                 :            : 
    3351                 :            :     const struct pl_frame *target = &pass.target;
    3352                 :        608 :     int out_w = abs(pl_rect_w(pass.dst_rect)),
    3353                 :        608 :         out_h = abs(pl_rect_h(pass.dst_rect));
    3354         [ -  + ]:        608 :     if (!out_w || !out_h)
    3355                 :          0 :         goto fallback;
    3356                 :            : 
    3357                 :            :     int fidx = 0;
    3358                 :            :     struct cached_frame frames[MAX_MIX_FRAMES];
    3359                 :            :     float weights[MAX_MIX_FRAMES];
    3360                 :            :     float wsum = 0.0;
    3361                 :            : 
    3362                 :            :     // Garbage collect the cache by evicting all frames from the cache that are
    3363                 :            :     // not determined to still be required
    3364         [ +  + ]:       1788 :     for (int i = 0; i < rr->frames.num; i++)
    3365                 :       1180 :         rr->frames.elem[i].evict = true;
    3366                 :            : 
    3367                 :            :     // Blur frame mixer according to vsync ratio (source / display)
    3368                 :            :     struct pl_filter_config mixer;
    3369         [ +  - ]:        608 :     if (params->frame_mixer) {
    3370                 :        608 :         mixer = *params->frame_mixer;
    3371         [ -  + ]:        608 :         mixer.blur = PL_DEF(mixer.blur, 1.0);
    3372         [ +  + ]:        992 :         for (int i = 1; i < images->num_frames; i++) {
    3373   [ +  +  +  + ]:        960 :             if (images->timestamps[i] >= 0.0 && images->timestamps[i - 1] < 0) {
    3374                 :        576 :                 float frame_dur = images->timestamps[i] - images->timestamps[i - 1];
    3375   [ -  +  -  - ]:        576 :                 if (images->vsync_duration > frame_dur && !params->skip_anti_aliasing)
    3376                 :          0 :                     mixer.blur *= images->vsync_duration / frame_dur;
    3377                 :            :                 break;
    3378                 :            :             }
    3379                 :            :         }
    3380                 :            :     }
    3381                 :            : 
    3382                 :            :     // Traverse the input frames and determine/prepare the ones we need
    3383   [ +  -  +  + ]:        620 :     bool single_frame = !params->frame_mixer || images->num_frames == 1;
    3384                 :        616 : retry:
    3385         [ +  + ]:       2360 :     for (int i = 0; i < images->num_frames; i++) {
    3386                 :       1752 :         uint64_t sig = images->signatures[i];
    3387                 :       1752 :         float rts = images->timestamps[i];
    3388                 :       1752 :         const struct pl_frame *img = images->frames[i];
    3389                 :       1752 :         PL_TRACE(rr, "Considering image with signature 0x%llx, rts %f",
    3390                 :            :                  (unsigned long long) sig, rts);
    3391                 :            : 
    3392                 :            :         // Combining images with different rotations is basically unfeasible
    3393         [ -  + ]:       1752 :         if (pl_rotation_normalize(img->rotation - refimg->rotation)) {
    3394                 :          0 :             PL_TRACE(rr, "  -> Skipping: incompatible rotation");
    3395                 :          0 :             continue;
    3396                 :            :         }
    3397                 :            : 
    3398                 :            :         float weight;
    3399         [ +  + ]:       1752 :         if (single_frame) {
    3400                 :            : 
    3401                 :            :             // Only render the refimg, ignore others
    3402         [ -  + ]:         20 :             if (img == refimg) {
    3403                 :            :                 weight = 1.0;
    3404                 :            :             } else {
    3405                 :          0 :                 PL_TRACE(rr, "  -> Skipping: no frame mixer");
    3406                 :          0 :                 continue;
    3407                 :            :             }
    3408                 :            : 
    3409                 :            :         // For backwards compatibility, treat !kernel as oversample
    3410   [ +  -  +  + ]:       1732 :         } else if (!mixer.kernel || mixer.kernel == &pl_filter_function_oversample) {
    3411                 :            : 
    3412                 :            :             // Compute the visible interval [rts, end] of this frame
    3413         [ +  + ]:        776 :             float end = i+1 < images->num_frames ? images->timestamps[i+1] : INFINITY;
    3414   [ +  +  -  + ]:        776 :             if (rts > images->vsync_duration || end < 0.0) {
    3415                 :        156 :                 PL_TRACE(rr, "  -> Skipping: no intersection with vsync");
    3416                 :        156 :                 continue;
    3417                 :            :             } else {
    3418         [ +  + ]:        620 :                 rts = PL_MAX(rts, 0.0);
    3419         [ +  + ]:        620 :                 end = PL_MIN(end, images->vsync_duration);
    3420         [ -  + ]:        620 :                 pl_assert(end >= rts);
    3421                 :            :             }
    3422                 :            : 
    3423                 :            :             // Weight is the fraction of vsync interval that frame is visible
    3424                 :        620 :             weight = (end - rts) / images->vsync_duration;
    3425                 :        620 :             PL_TRACE(rr, "  -> Frame [%f, %f] intersects [%f, %f] = weight %f",
    3426                 :            :                      rts, end, 0.0, images->vsync_duration, weight);
    3427                 :            : 
    3428         [ -  + ]:        620 :             if (weight < mixer.kernel->params[0]) {
    3429                 :          0 :                 PL_TRACE(rr, "     (culling due to threshold)");
    3430                 :            :                 weight = 0.0;
    3431                 :            :             }
    3432                 :            : 
    3433                 :            :         } else {
    3434                 :            : 
    3435                 :            :             const float radius = pl_filter_radius_bound(&mixer);
    3436         [ +  + ]:        956 :             if (fabsf(rts) >= radius) {
    3437                 :        208 :                 PL_TRACE(rr, "  -> Skipping: outside filter radius (%f)", radius);
    3438                 :        208 :                 continue;
    3439                 :            :             }
    3440                 :            : 
    3441                 :            :             // Weight is directly sampled from the filter
    3442                 :        748 :             weight = pl_filter_sample(&mixer, rts);
    3443                 :        748 :             PL_TRACE(rr, "  -> Filter offset %f = weight %f", rts, weight);
    3444                 :            : 
    3445                 :            :         }
    3446                 :            : 
    3447                 :            :         struct cached_frame *f = NULL;
    3448         [ +  + ]:       3304 :         for (int j = 0; j < rr->frames.num; j++) {
    3449         [ +  + ]:       2780 :             if (rr->frames.elem[j].signature == sig) {
    3450                 :            :                 f = &rr->frames.elem[j];
    3451                 :        864 :                 f->evict = false;
    3452                 :        864 :                 break;
    3453                 :            :             }
    3454                 :            :         }
    3455                 :            : 
    3456                 :            :         // Skip frames with negligible contributions. Do this after the loop
    3457                 :            :         // above to make sure these frames don't get evicted just yet, and
    3458                 :            :         // also exclude the reference image from this optimization to ensure
    3459                 :            :         // that we always have at least one frame.
    3460                 :            :         const float cutoff = 1e-3;
    3461   [ +  +  +  + ]:       1388 :         if (fabsf(weight) <= cutoff && img != refimg) {
    3462                 :        360 :             PL_TRACE(rr, "   -> Skipping: weight (%f) below threshold (%f)",
    3463                 :            :                      weight, cutoff);
    3464                 :        360 :             continue;
    3465                 :            :         }
    3466                 :            : 
    3467   [ +  +  +  -  :       1028 :         bool skip_cache = single_frame && (params->skip_caching_single_frame || par_info.trivial);
                   +  - ]
    3468         [ +  + ]:       1028 :         if (!f && skip_cache) {
    3469                 :          8 :             PL_TRACE(rr, "Single frame not found in cache, bypassing");
    3470                 :          8 :             goto fallback;
    3471                 :            :         }
    3472                 :            : 
    3473         [ +  + ]:       1020 :         if (!f) {
    3474                 :            :             // Signature does not exist in the cache at all yet,
    3475                 :            :             // so grow the cache by this entry.
    3476   [ +  +  -  +  :        320 :             PL_ARRAY_GROW(rr, rr->frames);
                   -  + ]
    3477                 :        320 :             f = &rr->frames.elem[rr->frames.num++];
    3478                 :        320 :             *f = (struct cached_frame) {
    3479                 :            :                 .signature = sig,
    3480                 :            :             };
    3481                 :            :         }
    3482                 :            : 
    3483                 :            :         // Check to see if we can blindly reuse this cache entry. This is the
    3484                 :            :         // case if either the params are compatible, or the user doesn't care
    3485                 :       1020 :         bool can_reuse = f->tex;
    3486         [ +  + ]:       1020 :         bool strict_reuse = skip_cache || single_frame ||
    3487         [ +  - ]:       1008 :                             !params->preserve_mixing_cache;
    3488         [ +  + ]:       1020 :         if (can_reuse && strict_reuse) {
    3489                 :        700 :             can_reuse = f->tex->params.w == out_w &&
    3490         [ -  + ]:        700 :                         f->tex->params.h == out_h &&
    3491   [ -  +  -  +  :        700 :                         pl_rect2d_eq(f->crop, img->crop) &&
             -  +  -  + ]
    3492   [ -  +  -  + ]:       1400 :                         f->params_hash == par_info.hash &&
    3493   [ +  -  -  + ]:       2100 :                         pl_color_space_equal(&f->color, &target->color) &&
    3494                 :        700 :                         pl_icc_profile_equal(&f->profile, &target->profile);
    3495                 :            :         }
    3496                 :            : 
    3497         [ -  + ]:       1020 :         if (!can_reuse && skip_cache) {
    3498                 :          0 :             PL_TRACE(rr, "Single frame cache entry invalid, bypassing");
    3499                 :          0 :             goto fallback;
    3500                 :            :         }
    3501                 :            : 
    3502         [ +  + ]:       1020 :         if (!can_reuse) {
    3503                 :            :             // If we can't reuse the entry, we need to re-render this frame
    3504                 :        320 :             PL_TRACE(rr, "  -> Cached texture missing or invalid.. (re)creating");
    3505         [ +  - ]:        320 :             if (!f->tex) {
    3506         [ +  + ]:        320 :                 if (PL_ARRAY_POP(rr->frame_fbos, &f->tex))
    3507                 :        304 :                     pl_tex_invalidate(rr->gpu, f->tex);
    3508                 :            :             }
    3509                 :            : 
    3510                 :        320 :             bool ok = pl_tex_recreate(rr->gpu, &f->tex, pl_tex_params(
    3511                 :            :                 .w = out_w,
    3512                 :            :                 .h = out_h,
    3513                 :            :                 .format = pass.fbofmt[4],
    3514                 :            :                 .sampleable = true,
    3515                 :            :                 .renderable = true,
    3516                 :            :                 .blit_dst = pass.fbofmt[4]->caps & PL_FMT_CAP_BLITTABLE,
    3517                 :            :                 .storable = pass.fbofmt[4]->caps & PL_FMT_CAP_STORABLE,
    3518                 :            :             ));
    3519                 :            : 
    3520         [ -  + ]:        320 :             if (!ok) {
    3521                 :          0 :                 PL_ERR(rr, "Could not create intermediate texture for "
    3522                 :            :                        "frame mixing.. disabling!");
    3523                 :          0 :                 rr->errors |= PL_RENDER_ERR_FRAME_MIXING;
    3524                 :          0 :                 goto fallback;
    3525                 :            :             }
    3526                 :            : 
    3527                 :        320 :             struct pass_state inter_pass = {
    3528                 :            :                 .rr = rr,
    3529                 :        320 :                 .params = pass.params,
    3530                 :            :                 .image = *img,
    3531                 :            :                 .target = *ptarget,
    3532                 :            :                 .info.stage = PL_RENDER_STAGE_FRAME,
    3533                 :            :                 .acquired = pass.acquired,
    3534                 :            :             };
    3535                 :            : 
    3536                 :            :             // Render a single frame up to `pass_output_target`
    3537                 :            :             memcpy(inter_pass.fbofmt, pass.fbofmt, sizeof(pass.fbofmt));
    3538         [ -  + ]:        320 :             if (!pass_init(&inter_pass, true))
    3539                 :          0 :                 goto fail;
    3540                 :            : 
    3541                 :        320 :             pass_begin_frame(&inter_pass);
    3542         [ -  + ]:        320 :             if (!(ok = pass_read_image(&inter_pass)))
    3543                 :          0 :                 goto inter_pass_error;
    3544         [ -  + ]:        320 :             if (!(ok = pass_scale_main(&inter_pass)))
    3545                 :          0 :                 goto inter_pass_error;
    3546                 :        320 :             pass_convert_colors(&inter_pass);
    3547                 :            : 
    3548         [ -  + ]:        320 :             pl_assert(inter_pass.img.sh); // guaranteed by `pass_convert_colors`
    3549                 :        320 :             pl_shader_set_alpha(inter_pass.img.sh, &inter_pass.img.repr,
    3550                 :            :                                 PL_ALPHA_PREMULTIPLIED); // for frame mixing
    3551                 :            : 
    3552   [ +  -  -  + ]:        320 :             pl_assert(inter_pass.img.w == out_w &&
    3553                 :            :                       inter_pass.img.h == out_h);
    3554                 :            : 
    3555                 :        320 :             ok = pl_dispatch_finish(rr->dp, pl_dispatch_params(
    3556                 :            :                 .shader = &inter_pass.img.sh,
    3557                 :            :                 .target = f->tex,
    3558                 :            :             ));
    3559         [ -  + ]:        320 :             if (!ok)
    3560                 :          0 :                 goto inter_pass_error;
    3561                 :            : 
    3562                 :        320 :             float sx = out_w / pl_rect_w(inter_pass.dst_rect),
    3563                 :        320 :                   sy = out_h / pl_rect_h(inter_pass.dst_rect);
    3564                 :            : 
    3565                 :        320 :             pl_transform2x2 shift = {
    3566                 :            :                 .mat.m = {{ sx, 0, }, { 0, sy, }},
    3567                 :            :                 .c = {
    3568                 :        320 :                     -sx * inter_pass.dst_rect.x0,
    3569                 :        320 :                     -sy * inter_pass.dst_rect.y0
    3570                 :            :                 },
    3571                 :            :             };
    3572                 :            : 
    3573         [ +  - ]:        320 :             if (inter_pass.rotation % PL_ROTATION_180 == PL_ROTATION_90) {
    3574                 :        320 :                 PL_SWAP(shift.mat.m[0][0], shift.mat.m[0][1]);
    3575                 :        320 :                 PL_SWAP(shift.mat.m[1][0], shift.mat.m[1][1]);
    3576                 :            :             }
    3577                 :            : 
    3578                 :        320 :             draw_overlays(&inter_pass, f->tex, inter_pass.img.comps, NULL,
    3579                 :            :                           inter_pass.image.overlays,
    3580                 :            :                           inter_pass.image.num_overlays,
    3581                 :            :                           inter_pass.img.color,
    3582                 :            :                           inter_pass.img.repr,
    3583                 :            :                           &shift);
    3584                 :            : 
    3585                 :        320 :             f->params_hash = par_info.hash;
    3586                 :        320 :             f->crop = img->crop;
    3587                 :        320 :             f->color = inter_pass.img.color;
    3588                 :        320 :             f->comps = inter_pass.img.comps;
    3589                 :        320 :             f->profile = target->profile;
    3590                 :            :             // fall through
    3591                 :            : 
    3592                 :        320 : inter_pass_error:
    3593                 :        320 :             inter_pass.acquired.target = false; // don't release target
    3594                 :        320 :             pass_uninit(&inter_pass);
    3595         [ -  + ]:        320 :             if (!ok)
    3596                 :          0 :                 goto fail;
    3597                 :            :         }
    3598                 :            : 
    3599         [ -  + ]:       1020 :         pl_assert(fidx < MAX_MIX_FRAMES);
    3600                 :       1020 :         frames[fidx] = *f;
    3601                 :       1020 :         weights[fidx] = weight;
    3602                 :       1020 :         wsum += weight;
    3603                 :       1020 :         fidx++;
    3604                 :            :     }
    3605                 :            : 
    3606                 :            :     // Evict the frames we *don't* need
    3607         [ +  + ]:       2108 :     for (int i = 0; i < rr->frames.num; ) {
    3608         [ +  + ]:       1500 :         if (rr->frames.elem[i].evict) {
    3609                 :        316 :             PL_TRACE(rr, "Evicting frame with signature %llx from cache",
    3610                 :            :                      (unsigned long long) rr->frames.elem[i].signature);
    3611   [ +  +  -  +  :        316 :             PL_ARRAY_APPEND(rr, rr->frame_fbos, rr->frames.elem[i].tex);
                   -  + ]
    3612   [ -  +  -  -  :        316 :             PL_ARRAY_REMOVE_AT(rr->frames, i);
                   -  + ]
    3613                 :        316 :             continue;
    3614                 :            :         } else {
    3615                 :       1184 :             i++;
    3616                 :            :         }
    3617                 :            :     }
    3618                 :            : 
    3619                 :            :     // If we got back no frames, retry with ZOH semantics
    3620         [ +  + ]:        608 :     if (!fidx) {
    3621         [ -  + ]:          8 :         pl_assert(!single_frame);
    3622                 :            :         single_frame = true;
    3623                 :          8 :         goto retry;
    3624                 :            :     }
    3625                 :            : 
    3626                 :            :     // Sample and mix the output color
    3627                 :        600 :     pass_begin_frame(&pass);
    3628                 :        600 :     pass.info.count = fidx;
    3629         [ -  + ]:        600 :     pl_assert(fidx > 0);
    3630                 :            : 
    3631                 :        600 :     pl_shader sh = pl_dispatch_begin(rr->dp);
    3632         [ +  + ]:        816 :     sh_describef(sh, "frame mixing (%d frame%s)", fidx, fidx > 1 ? "s" : "");
    3633                 :        600 :     sh->output = PL_SHADER_SIG_COLOR;
    3634                 :        600 :     sh->output_w = out_w;
    3635                 :        600 :     sh->output_h = out_h;
    3636                 :            : 
    3637                 :        600 :     GLSL("vec4 color;                   \n"
    3638                 :            :          "// pl_render_image_mix        \n"
    3639                 :            :          "{                             \n"
    3640                 :            :          "vec4 mix_color = vec4(0.0);   \n");
    3641                 :            : 
    3642                 :            :     int comps = 0;
    3643         [ +  + ]:       1620 :     for (int i = 0; i < fidx; i++) {
    3644                 :       1020 :         const struct pl_tex_params *tpars = &frames[i].tex->params;
    3645                 :            : 
    3646                 :            :         // Use linear sampling if desired and possible
    3647                 :            :         enum pl_tex_sample_mode sample_mode = PL_TEX_SAMPLE_NEAREST;
    3648   [ +  -  +  - ]:       1020 :         if ((tpars->w != out_w || tpars->h != out_h) &&
    3649         [ #  # ]:          0 :             (tpars->format->caps & PL_FMT_CAP_LINEAR))
    3650                 :            :         {
    3651                 :            :             sample_mode = PL_TEX_SAMPLE_LINEAR;
    3652                 :            :         }
    3653                 :            : 
    3654                 :       1020 :         ident_t pos, tex = sh_bind(sh, frames[i].tex, PL_TEX_ADDRESS_CLAMP,
    3655                 :            :                                    sample_mode, "frame", NULL, &pos, NULL);
    3656                 :            : 
    3657                 :       1020 :         GLSL("color = textureLod("$", "$", 0.0); \n", tex, pos);
    3658                 :            : 
    3659                 :            :         // Note: This ignores differences in ICC profile, which we decide to
    3660                 :            :         // just simply not care about. Doing that properly would require
    3661                 :            :         // converting between different image profiles, and the headache of
    3662                 :            :         // finagling that state is just not worth it because this is an
    3663                 :            :         // exceptionally unlikely hypothetical.
    3664                 :            :         //
    3665                 :            :         // This also ignores differences in HDR metadata, which we deliberately
    3666                 :            :         // ignore because it causes aggressive shader recompilation.
    3667                 :       1020 :         struct pl_color_space frame_csp = frames[i].color;
    3668                 :       1020 :         struct pl_color_space mix_csp = target->color;
    3669                 :       1020 :         frame_csp.hdr = mix_csp.hdr = (struct pl_hdr_metadata) {0};
    3670                 :       1020 :         pl_shader_color_map_ex(sh, NULL, pl_color_map_args(frame_csp, mix_csp));
    3671                 :            : 
    3672                 :       1020 :         float weight = weights[i] / wsum;
    3673                 :       1020 :         GLSL("mix_color += vec4("$") * color; \n", SH_FLOAT_DYN(weight));
    3674                 :       1020 :         comps = PL_MAX(comps, frames[i].comps);
    3675                 :            :     }
    3676                 :            : 
    3677                 :        600 :     GLSL("color = mix_color; \n"
    3678                 :            :          "}                  \n");
    3679                 :            : 
    3680                 :            :     // Dispatch this to the destination
    3681                 :        600 :     pass.img = (struct img) {
    3682                 :            :         .sh = sh,
    3683                 :            :         .w = out_w,
    3684                 :            :         .h = out_h,
    3685                 :            :         .comps = comps,
    3686                 :            :         .color = target->color,
    3687                 :            :         .repr = {
    3688                 :            :             .sys = PL_COLOR_SYSTEM_RGB,
    3689                 :            :             .levels = PL_COLOR_LEVELS_PC,
    3690         [ +  - ]:        600 :             .alpha = comps >= 4 ? PL_ALPHA_PREMULTIPLIED : PL_ALPHA_NONE,
    3691                 :            :         },
    3692                 :            :     };
    3693                 :            : 
    3694         [ -  + ]:        600 :     if (!pass_output_target(&pass))
    3695                 :          0 :         goto fallback;
    3696                 :            : 
    3697                 :        600 :     pass_uninit(&pass);
    3698                 :        600 :     return true;
    3699                 :            : 
    3700                 :            : fail:
    3701                 :          0 :     PL_ERR(rr, "Could not render image for frame mixing.. disabling!");
    3702                 :          0 :     rr->errors |= PL_RENDER_ERR_FRAME_MIXING;
    3703                 :            :     // fall through
    3704                 :            : 
    3705                 :          8 : fallback:
    3706                 :          8 :     pass_uninit(&pass);
    3707                 :          8 :     return pl_render_image(rr, refimg, ptarget, params);
    3708                 :            : 
    3709                 :            : error: // for parameter validation failures
    3710                 :            :     return false;
    3711                 :            : }
    3712                 :            : 
    3713                 :          8 : void pl_frames_infer_mix(pl_renderer rr, const struct pl_frame_mix *mix,
    3714                 :            :                          struct pl_frame *target, struct pl_frame *out_ref)
    3715                 :            : {
    3716                 :          8 :     struct pass_state pass = {
    3717                 :            :         .rr     = rr,
    3718                 :            :         .target = *target,
    3719                 :            :     };
    3720                 :            : 
    3721                 :          8 :     const struct pl_frame *refimg = pl_frame_mix_nearest(mix);
    3722         [ +  + ]:          8 :     if (refimg) {
    3723                 :          4 :         pass.image = *refimg;
    3724                 :            :     } else {
    3725                 :          4 :         pass.src_ref = -1;
    3726                 :            :     }
    3727                 :            : 
    3728                 :          8 :     pass_fix_frames(&pass);
    3729                 :          8 :     *target = pass.target;
    3730         [ +  - ]:          8 :     if (out_ref)
    3731                 :          8 :         *out_ref = pass.image;
    3732                 :          8 : }
    3733                 :            : 
    3734                 :          5 : void pl_frame_set_chroma_location(struct pl_frame *frame,
    3735                 :            :                                   enum pl_chroma_location chroma_loc)
    3736                 :            : {
    3737                 :          5 :     pl_tex ref = frame->planes[frame_ref(frame)].texture;
    3738                 :            : 
    3739         [ -  + ]:          5 :     if (ref) {
    3740                 :            :         // Texture dimensions are already known, so apply the chroma location
    3741                 :            :         // only to subsampled planes
    3742                 :          5 :         int ref_w = ref->params.w, ref_h = ref->params.h;
    3743                 :            : 
    3744         [ +  + ]:         20 :         for (int i = 0; i < frame->num_planes; i++) {
    3745                 :            :             struct pl_plane *plane = &frame->planes[i];
    3746                 :         15 :             pl_tex tex = plane->texture;
    3747   [ +  +  -  + ]:         15 :             bool subsampled = tex->params.w < ref_w || tex->params.h < ref_h;
    3748                 :            :             if (subsampled)
    3749                 :         10 :                 pl_chroma_location_offset(chroma_loc, &plane->shift_x, &plane->shift_y);
    3750                 :            :         }
    3751                 :            :     } else {
    3752                 :            :         // Texture dimensions are not yet known, so apply the chroma location
    3753                 :            :         // to all chroma planes, regardless of subsampling
    3754         [ #  # ]:          0 :         for (int i = 0; i < frame->num_planes; i++) {
    3755                 :          0 :             struct pl_plane *plane = &frame->planes[i];
    3756         [ #  # ]:          0 :             if (detect_plane_type(plane, &frame->repr) == PLANE_CHROMA)
    3757                 :          0 :                 pl_chroma_location_offset(chroma_loc, &plane->shift_x, &plane->shift_y);
    3758                 :            :         }
    3759                 :            :     }
    3760                 :          5 : }
    3761                 :            : 
    3762                 :         50 : void pl_frame_from_swapchain(struct pl_frame *out_frame,
    3763                 :            :                              const struct pl_swapchain_frame *frame)
    3764                 :            : {
    3765                 :         50 :     pl_tex fbo = frame->fbo;
    3766                 :         50 :     int num_comps = fbo->params.format->num_components;
    3767         [ +  + ]:         50 :     if (frame->color_repr.alpha == PL_ALPHA_NONE)
    3768                 :         40 :         num_comps = PL_MIN(num_comps, 3);
    3769                 :            : 
    3770                 :         50 :     *out_frame = (struct pl_frame) {
    3771                 :            :         .num_planes = 1,
    3772                 :            :         .planes = {{
    3773                 :            :             .texture = fbo,
    3774                 :         50 :             .flipped = frame->flipped,
    3775                 :            :             .components = num_comps,
    3776                 :            :             .component_mapping = {0, 1, 2, 3},
    3777                 :            :         }},
    3778                 :         50 :         .crop = { 0, 0, fbo->params.w, fbo->params.h },
    3779                 :            :         .repr = frame->color_repr,
    3780                 :            :         .color = frame->color_space,
    3781                 :            :     };
    3782                 :         50 : }
    3783                 :            : 
    3784                 :       1077 : bool pl_frame_is_cropped(const struct pl_frame *frame)
    3785                 :            : {
    3786         [ +  + ]:       1077 :     int x0 = roundf(PL_MIN(frame->crop.x0, frame->crop.x1)),
    3787         [ +  + ]:       1077 :         y0 = roundf(PL_MIN(frame->crop.y0, frame->crop.y1)),
    3788         [ +  + ]:       1077 :         x1 = roundf(PL_MAX(frame->crop.x0, frame->crop.x1)),
    3789         [ +  + ]:       1077 :         y1 = roundf(PL_MAX(frame->crop.y0, frame->crop.y1));
    3790                 :            : 
    3791                 :       1077 :     pl_tex ref = frame->planes[frame_ref(frame)].texture;
    3792         [ -  + ]:       1077 :     pl_assert(ref);
    3793                 :            : 
    3794         [ -  + ]:       1077 :     if (!x0 && !x1)
    3795                 :          0 :         x1 = ref->params.w;
    3796         [ -  + ]:       1077 :     if (!y0 && !y1)
    3797                 :          0 :         y1 = ref->params.h;
    3798                 :            : 
    3799   [ +  -  +  +  :       1077 :     return x0 > 0 || y0 > 0 || x1 < ref->params.w || y1 < ref->params.h;
                   -  + ]
    3800                 :            : }
    3801                 :            : 
    3802                 :          0 : void pl_frame_clear_tiles(pl_gpu gpu, const struct pl_frame *frame,
    3803                 :            :                           const float tile_colors[2][3], int tile_size)
    3804                 :            : {
    3805                 :          0 :     struct pl_color_repr repr = frame->repr;
    3806                 :          0 :     pl_transform3x3 tr = pl_color_repr_decode(&repr, NULL);
    3807                 :          0 :     pl_transform3x3_invert(&tr);
    3808                 :            : 
    3809                 :            :     float encoded[2][3];
    3810                 :            :     memcpy(encoded, tile_colors, sizeof(encoded));
    3811                 :          0 :     pl_transform3x3_apply(&tr, encoded[0]);
    3812                 :          0 :     pl_transform3x3_apply(&tr, encoded[1]);
    3813                 :            : 
    3814                 :          0 :     pl_tex ref = frame->planes[frame_ref(frame)].texture;
    3815                 :            : 
    3816         [ #  # ]:          0 :     for (int p = 0; p < frame->num_planes; p++) {
    3817                 :            :         const struct pl_plane *plane = &frame->planes[p];
    3818                 :          0 :         float tiles[2][3] = {0};
    3819         [ #  # ]:          0 :         for (int c = 0; c < plane->components; c++) {
    3820                 :          0 :             int ch = plane->component_mapping[c];
    3821         [ #  # ]:          0 :             if (ch >= 0 && ch < 3) {
    3822                 :          0 :                 tiles[0][c] = encoded[0][plane->component_mapping[c]];
    3823                 :          0 :                 tiles[1][c] = encoded[1][plane->component_mapping[c]];
    3824                 :            :             }
    3825                 :            :         }
    3826                 :            : 
    3827                 :          0 :         float rx = (float) plane->texture->params.w / ref->params.w,
    3828                 :          0 :               ry = (float) plane->texture->params.h / ref->params.h;
    3829         [ #  # ]:          0 :         float rrx = rx >= 1 ? roundf(rx) : 1.0 / roundf(1.0 / rx),
    3830         [ #  # ]:          0 :               rry = ry >= 1 ? roundf(ry) : 1.0 / roundf(1.0 / ry);
    3831                 :          0 :         int size_x = tile_size * rrx, size_y = tile_size * rry;
    3832                 :            : 
    3833                 :          0 :         pl_dispatch dp = pl_gpu_dispatch(gpu);
    3834                 :          0 :         pl_shader sh = pl_dispatch_begin(dp);
    3835                 :          0 :         sh->output = PL_SHADER_SIG_COLOR;
    3836                 :          0 :         GLSL("// pl_frame_clear_tiles (plane %d)                    \n"
    3837                 :            :              "vec4 color;                                           \n"
    3838                 :            :              "vec2 outcoord = gl_FragCoord.xy * vec2("$", "$");     \n"
    3839                 :            :              "bvec2 tile = lessThan(fract(outcoord), vec2(0.5));    \n"
    3840                 :            :              "color.rgb = tile.x == tile.y ? vec3("$", "$", "$")    \n"
    3841                 :            :              "                             : vec3("$", "$", "$");   \n"
    3842                 :            :              "color.a = 1.0;                                        \n",
    3843                 :            :              p, SH_FLOAT(1.0 / size_x), SH_FLOAT(1.0 / size_y),
    3844                 :            :              SH_FLOAT(tiles[0][0]), SH_FLOAT(tiles[0][1]), SH_FLOAT(tiles[0][2]),
    3845                 :            :              SH_FLOAT(tiles[1][0]), SH_FLOAT(tiles[1][1]), SH_FLOAT(tiles[1][2]));
    3846                 :            : 
    3847                 :          0 :         pl_dispatch_finish(dp, pl_dispatch_params(
    3848                 :            :             .shader = &sh,
    3849                 :            :             .target = plane->texture,
    3850                 :            :         ));
    3851                 :            :     }
    3852                 :          0 : }
    3853                 :            : 
    3854                 :        120 : void pl_frame_clear_rgba(pl_gpu gpu, const struct pl_frame *frame,
    3855                 :            :                          const float rgba[4])
    3856                 :            : {
    3857                 :        120 :     struct pl_color_repr repr = frame->repr;
    3858                 :        120 :     pl_transform3x3 tr = pl_color_repr_decode(&repr, NULL);
    3859                 :        120 :     pl_transform3x3_invert(&tr);
    3860                 :            : 
    3861                 :        120 :     float encoded[3] = { rgba[0], rgba[1], rgba[2] };
    3862                 :        120 :     pl_transform3x3_apply(&tr, encoded);
    3863                 :            : 
    3864         [ -  + ]:        120 :     float mult = frame->repr.alpha == PL_ALPHA_PREMULTIPLIED ? rgba[3] : 1.0;
    3865         [ +  + ]:        240 :     for (int p = 0; p < frame->num_planes; p++) {
    3866                 :            :         const struct pl_plane *plane =  &frame->planes[p];
    3867                 :        120 :         float clear[4] = { 0.0, 0.0, 0.0, rgba[3] };
    3868         [ +  + ]:        480 :         for (int c = 0; c < plane->components; c++) {
    3869                 :        360 :             int ch = plane->component_mapping[c];
    3870         [ +  - ]:        360 :             if (ch >= 0 && ch < 3)
    3871                 :        360 :                 clear[c] = mult * encoded[plane->component_mapping[c]];
    3872                 :            :         }
    3873                 :            : 
    3874                 :        120 :         pl_tex_clear(gpu, plane->texture, clear);
    3875                 :            :     }
    3876                 :        120 : }
    3877                 :            : 
    3878                 :        475 : struct pl_render_errors pl_renderer_get_errors(pl_renderer rr)
    3879                 :            : {
    3880                 :        475 :     return (struct pl_render_errors) {
    3881                 :        475 :         .errors = rr->errors,
    3882                 :        475 :         .disabled_hooks = rr->disabled_hooks.elem,
    3883                 :        475 :         .num_disabled_hooks = rr->disabled_hooks.num,
    3884                 :            :     };
    3885                 :            : }
    3886                 :            : 
    3887                 :          2 : void pl_renderer_reset_errors(pl_renderer rr,
    3888                 :            :                               const struct pl_render_errors *errors)
    3889                 :            : {
    3890         [ -  + ]:          2 :     if (!errors) {
    3891                 :            :         // Reset everything
    3892                 :          0 :         rr->errors = PL_RENDER_ERR_NONE;
    3893                 :          0 :         rr->disabled_hooks.num = 0;
    3894                 :          0 :         return;
    3895                 :            :     }
    3896                 :            : 
    3897                 :            :     // Reset only requested errors
    3898                 :          2 :     rr->errors &= ~errors->errors;
    3899                 :            : 
    3900                 :            :     // Not clearing hooks
    3901         [ +  - ]:          2 :     if (!(errors->errors & PL_RENDER_ERR_HOOKS))
    3902                 :          2 :         goto done;
    3903                 :            : 
    3904                 :            :     // Remove all hook signatures
    3905         [ #  # ]:          0 :     if (!errors->num_disabled_hooks) {
    3906                 :          0 :         rr->disabled_hooks.num = 0;
    3907                 :          0 :         goto done;
    3908                 :            :     }
    3909                 :            : 
    3910                 :            :     // At this point we require valid array of hooks
    3911         [ #  # ]:          0 :     if (!errors->disabled_hooks) {
    3912                 :          0 :         assert(errors->disabled_hooks);
    3913                 :            :         goto done;
    3914                 :            :     }
    3915                 :            : 
    3916         [ #  # ]:          0 :     for (int i = 0; i < errors->num_disabled_hooks; i++) {
    3917         [ #  # ]:          0 :         for (int j = 0; j < rr->disabled_hooks.num; j++) {
    3918                 :            :             // Remove only requested hook signatures
    3919         [ #  # ]:          0 :             if (rr->disabled_hooks.elem[j] == errors->disabled_hooks[i]) {
    3920         [ #  # ]:          0 :                 PL_ARRAY_REMOVE_AT(rr->disabled_hooks, j);
    3921                 :          0 :                 break;
    3922                 :            :             }
    3923                 :            :         }
    3924                 :            :     }
    3925                 :            : 
    3926                 :          0 :     done:
    3927         [ -  + ]:          2 :         if (rr->disabled_hooks.num)
    3928                 :          0 :             rr->errors |= PL_RENDER_ERR_HOOKS;
    3929                 :            :         return;
    3930                 :            : }

Generated by: LCOV version 1.16