LCOV - code coverage report
Current view: top level - src/utils - upload.c (source / functions) Hit Total Coverage
Test: Code coverage Lines: 119 164 72.6 %
Date: 2025-03-29 09:04:10 Functions: 7 7 100.0 %
Legend: Lines: hit not hit | Branches: + taken - not taken # not executed Branches: 85 136 62.5 %

           Branch data     Line data    Source code
       1                 :            : /*
       2                 :            :  * This file is part of libplacebo.
       3                 :            :  *
       4                 :            :  * libplacebo is free software; you can redistribute it and/or
       5                 :            :  * modify it under the terms of the GNU Lesser General Public
       6                 :            :  * License as published by the Free Software Foundation; either
       7                 :            :  * version 2.1 of the License, or (at your option) any later version.
       8                 :            :  *
       9                 :            :  * libplacebo is distributed in the hope that it will be useful,
      10                 :            :  * but WITHOUT ANY WARRANTY; without even the implied warranty of
      11                 :            :  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      12                 :            :  * GNU Lesser General Public License for more details.
      13                 :            :  *
      14                 :            :  * You should have received a copy of the GNU Lesser General Public
      15                 :            :  * License along with libplacebo. If not, see <http://www.gnu.org/licenses/>.
      16                 :            :  */
      17                 :            : 
      18                 :            : #include "log.h"
      19                 :            : #include "common.h"
      20                 :            : #include "gpu.h"
      21                 :            : 
      22                 :            : #include <libplacebo/utils/upload.h>
      23                 :            : 
      24                 :            : #define MAX_COMPS 4
      25                 :            : 
      26                 :            : struct comp {
      27                 :            :     int order; // e.g. 0, 1, 2, 3 for RGBA
      28                 :            :     int size;  // size in bits
      29                 :            :     int shift; // bit-shift / offset in bits
      30                 :            : };
      31                 :            : 
      32                 :       2088 : static int compare_comp(const void *pa, const void *pb)
      33                 :            : {
      34                 :            :     const struct comp *a = pa, *b = pb;
      35                 :            : 
      36                 :            :     // Move all of the components with a size of 0 to the end, so they can
      37                 :            :     // be ignored outright
      38   [ +  +  +  + ]:       2088 :     if (a->size && !b->size)
      39                 :            :         return -1;
      40   [ +  +  +  + ]:       1493 :     if (b->size && !a->size)
      41                 :            :         return 1;
      42                 :            : 
      43                 :            :     // Otherwise, just compare based on the shift
      44                 :       1170 :     return PL_CMP(a->shift, b->shift);
      45                 :            : }
      46                 :            : 
      47                 :        475 : void pl_plane_data_from_comps(struct pl_plane_data *data, int size[4],
      48                 :            :                               int shift[4])
      49                 :            : {
      50                 :            :     struct comp comps[MAX_COMPS];
      51         [ +  + ]:       2375 :     for (int i = 0; i < PL_ARRAY_SIZE(comps); i++) {
      52                 :       1900 :         comps[i].order = i;
      53                 :       1900 :         comps[i].size = size[i];
      54                 :       1900 :         comps[i].shift = shift[i];
      55                 :            :     }
      56                 :            : 
      57                 :            :     // Sort the components by shift
      58                 :        475 :     qsort(comps, MAX_COMPS, sizeof(struct comp), compare_comp);
      59                 :            : 
      60                 :            :     // Generate the resulting component size/pad/map
      61                 :            :     int offset = 0;
      62         [ +  + ]:       2375 :     for (int i = 0; i < MAX_COMPS; i++)  {
      63         [ +  + ]:       1900 :         if (comps[i].size) {
      64         [ -  + ]:        651 :             assert(comps[i].shift >= offset);
      65                 :        651 :             data->component_size[i] = comps[i].size;
      66                 :        651 :             data->component_pad[i] = comps[i].shift - offset;
      67                 :        651 :             data->component_map[i] = comps[i].order;
      68                 :        651 :             offset += data->component_size[i] + data->component_pad[i];
      69                 :            :         } else {
      70                 :            :             // Clear the superfluous entries for sanity
      71                 :       1249 :             data->component_size[i] = 0;
      72                 :       1249 :             data->component_pad[i] = 0;
      73                 :       1249 :             data->component_map[i] = 0;
      74                 :            :         }
      75                 :            :     }
      76                 :        475 : }
      77                 :            : 
      78                 :          7 : void pl_plane_data_from_mask(struct pl_plane_data *data, uint64_t mask[4])
      79                 :            : {
      80                 :            :     int size[4];
      81                 :            :     int shift[4];
      82                 :            : 
      83         [ +  + ]:         35 :     for (int i = 0; i < PL_ARRAY_SIZE(size); i++) {
      84                 :         28 :         size[i] = __builtin_popcountll(mask[i]);
      85         [ +  + ]:         28 :         shift[i] = PL_MAX(0, __builtin_ffsll(mask[i]) - 1);
      86                 :            : 
      87                 :            :         // Sanity checking
      88                 :         28 :         uint64_t mask_reconstructed = (1LLU << size[i]) - 1;
      89                 :         28 :         mask_reconstructed <<= shift[i];
      90         [ -  + ]:         28 :         pl_assert(mask_reconstructed == mask[i]);
      91                 :            :     }
      92                 :            : 
      93                 :          7 :     pl_plane_data_from_comps(data, size, shift);
      94                 :          7 : }
      95                 :            : 
      96                 :        438 : bool pl_plane_data_align(struct pl_plane_data *data, struct pl_bit_encoding *out_bits)
      97                 :            : {
      98                 :        438 :     struct pl_plane_data aligned = *data;
      99                 :            :     struct pl_bit_encoding bits = {0};
     100                 :            : 
     101                 :            :     int offset = 0;
     102                 :            : 
     103                 :            : #define SET_TEST(var, value)                \
     104                 :            :     do {                                    \
     105                 :            :         if (offset == 0) {                  \
     106                 :            :             (var) = (value);                \
     107                 :            :         } else if ((var) != (value)) {      \
     108                 :            :             goto misaligned;                \
     109                 :            :         }                                   \
     110                 :            :     } while (0)
     111                 :            : 
     112         [ +  + ]:       1031 :     for (int i = 0; i < MAX_COMPS; i++) {
     113         [ +  + ]:       1008 :         if (!aligned.component_size[i])
     114                 :            :             break;
     115                 :            : 
     116                 :            :         // Can't meaningfully align alpha channel, so just skip it. This is a
     117                 :            :         // limitation of the fact that `pl_bit_encoding` only applies to the
     118                 :            :         // main color channels, and changing this would be very nontrivial.
     119         [ +  + ]:        608 :         if (aligned.component_map[i] == PL_CHANNEL_A)
     120                 :         23 :             continue;
     121                 :            : 
     122                 :            :         // Color depth is the original component size, before alignment
     123   [ +  +  +  + ]:        585 :         SET_TEST(bits.color_depth, aligned.component_size[i]);
     124                 :            : 
     125                 :            :         // Try consuming padding of the current component to align down. This
     126                 :            :         // corresponds to an extra bit shift to the left.
     127                 :        577 :         int comp_start = offset + aligned.component_pad[i];
     128                 :        577 :         int left_delta = comp_start - PL_ALIGN2(comp_start - 7, 8);
     129                 :        577 :         left_delta = PL_MIN(left_delta, aligned.component_pad[i]);
     130                 :        577 :         aligned.component_pad[i] -= left_delta;
     131                 :        577 :         aligned.component_size[i] += left_delta;
     132   [ +  +  -  + ]:        577 :         SET_TEST(bits.bit_shift, left_delta);
     133                 :            : 
     134                 :            :         // Try consuming padding of the next component to align up. This
     135                 :            :         // corresponds to simply ignoring some extra 0s on the end.
     136                 :        577 :         int comp_end = comp_start + aligned.component_size[i] - left_delta;
     137                 :        577 :         int right_delta = PL_ALIGN2(comp_end, 8) - comp_end;
     138   [ +  +  +  + ]:        577 :         if (i+1 == MAX_COMPS || !aligned.component_size[i+1]) {
     139                 :            :             // This is the last component, so we can be greedy
     140                 :        413 :             aligned.component_size[i] += right_delta;
     141                 :            :         } else {
     142                 :        164 :             right_delta = PL_MIN(right_delta, aligned.component_pad[i+1]);
     143                 :        164 :             aligned.component_pad[i+1] -= right_delta;
     144                 :        164 :             aligned.component_size[i] += right_delta;
     145                 :            :         }
     146                 :            : 
     147                 :            :         // Sample depth is the new total component size, including padding
     148   [ +  +  +  + ]:        577 :         SET_TEST(bits.sample_depth, aligned.component_size[i]);
     149                 :            : 
     150                 :        570 :         offset += aligned.component_pad[i] + aligned.component_size[i];
     151                 :            :     }
     152                 :            : 
     153                 :            :     // Easy sanity check, to make sure that we don't exceed the known stride
     154   [ +  +  -  + ]:        423 :     if (aligned.pixel_stride && offset > aligned.pixel_stride * 8)
     155                 :          0 :         goto misaligned;
     156                 :            : 
     157                 :        423 :     *data = aligned;
     158         [ +  - ]:        423 :     if (out_bits)
     159                 :        423 :         *out_bits = bits;
     160                 :            :     return true;
     161                 :            : 
     162                 :         15 : misaligned:
     163                 :            :     // Can't properly align anything, so just do a no-op
     164         [ +  - ]:         15 :     if (out_bits)
     165                 :         15 :         *out_bits = (struct pl_bit_encoding) {0};
     166                 :            :     return false;
     167                 :            : }
     168                 :            : 
     169                 :         29 : pl_fmt pl_plane_find_fmt(pl_gpu gpu, int out_map[4], const struct pl_plane_data *data)
     170                 :            : {
     171                 :         29 :     int dummy[4] = {0};
     172         [ +  + ]:         29 :     out_map = PL_DEF(out_map, dummy);
     173                 :            : 
     174                 :            :     // Endian swapping requires compute shaders (currently)
     175   [ +  -  -  - ]:         29 :     if (data->swapped && !gpu->limits.max_ssbo_size)
     176                 :            :         return NULL;
     177                 :            : 
     178                 :            :     // Count the number of components and initialize out_map
     179                 :            :     int num = 0;
     180         [ +  + ]:        145 :     for (int i = 0; i < PL_ARRAY_SIZE(data->component_size); i++) {
     181                 :        116 :         out_map[i] = -1;
     182         [ +  + ]:        116 :         if (data->component_size[i])
     183                 :         29 :             num = i+1;
     184                 :            :     }
     185                 :            : 
     186         [ -  + ]:        242 :     for (int n = 0; n < gpu->num_formats; n++) {
     187                 :        242 :         pl_fmt fmt = gpu->formats[n];
     188   [ +  -  -  + ]:        242 :         if (fmt->opaque || fmt->num_components < num)
     189                 :          0 :             continue;
     190   [ +  +  +  + ]:        242 :         if (fmt->type != data->type || fmt->texel_size != data->pixel_stride)
     191                 :        184 :             continue;
     192         [ -  + ]:         58 :         if (!(fmt->caps & PL_FMT_CAP_SAMPLEABLE))
     193                 :          0 :             continue;
     194                 :            : 
     195                 :            :         int idx = 0;
     196                 :            : 
     197                 :            :         // Try mapping all pl_plane_data components to texture components
     198         [ +  + ]:         87 :         for (int i = 0; i < num; i++) {
     199                 :            :             // If there's padding we have to map it to an unused physical
     200                 :            :             // component first
     201                 :         58 :             int pad = data->component_pad[i];
     202   [ -  +  -  -  :         58 :             if (pad && (idx >= 4 || fmt->host_bits[idx++] != pad))
                   -  - ]
     203                 :          0 :                 goto next_fmt;
     204                 :            : 
     205                 :            :             // Otherwise, try and match this component
     206                 :         58 :             int size = data->component_size[i];
     207   [ +  -  +  -  :         58 :             if (size && (idx >= 4 || fmt->host_bits[idx] != size))
                   +  + ]
     208                 :         29 :                 goto next_fmt;
     209                 :         29 :             out_map[idx++] = data->component_map[i];
     210                 :            :         }
     211                 :            : 
     212                 :            :         // Reject misaligned formats, check this last to only log such errors
     213                 :            :         // if this is the only thing preventing a format from being used, as
     214                 :            :         // this is likely an issue in the API usage.
     215         [ -  + ]:         29 :         if (data->row_stride % fmt->texel_align) {
     216                 :          0 :             PL_WARN(gpu, "Rejecting texture format '%s' due to misalignment: "
     217                 :            :                     "Row stride %zu is not a clean multiple of texel size %zu! "
     218                 :            :                     "This is likely an API usage bug.",
     219                 :            :                     fmt->name, data->row_stride, fmt->texel_align);
     220                 :          0 :             continue;
     221                 :            :         }
     222                 :            : 
     223                 :            :         return fmt;
     224                 :            : 
     225                 :        213 : next_fmt: ; // acts as `continue`
     226                 :            :     }
     227                 :            : 
     228                 :            :     return NULL;
     229                 :            : }
     230                 :            : 
     231                 :         19 : bool pl_upload_plane(pl_gpu gpu, struct pl_plane *out_plane,
     232                 :            :                      pl_tex *tex, const struct pl_plane_data *data)
     233                 :            : {
     234         [ -  + ]:         19 :     pl_assert(!data->buf ^ !data->pixels); // exactly one
     235                 :            : 
     236                 :            :     int out_map[4];
     237                 :         19 :     pl_fmt fmt = pl_plane_find_fmt(gpu, out_map, data);
     238         [ -  + ]:         19 :     if (!fmt) {
     239                 :          0 :         PL_ERR(gpu, "Failed picking any compatible texture format for a plane!");
     240                 :          0 :         return false;
     241                 :            : 
     242                 :            :         // TODO: try soft-converting to a supported format using e.g zimg?
     243                 :            :     }
     244                 :            : 
     245                 :         19 :     bool ok = pl_tex_recreate(gpu, tex, pl_tex_params(
     246                 :            :         .w = data->width,
     247                 :            :         .h = data->height,
     248                 :            :         .format = fmt,
     249                 :            :         .sampleable = true,
     250                 :            :         .host_writable = true,
     251                 :            :         .blit_src = fmt->caps & PL_FMT_CAP_BLITTABLE,
     252                 :            :     ));
     253                 :            : 
     254         [ -  + ]:         19 :     if (!ok) {
     255                 :          0 :         PL_ERR(gpu, "Failed initializing plane texture!");
     256                 :          0 :         return false;
     257                 :            :     }
     258                 :            : 
     259         [ +  - ]:         19 :     if (out_plane) {
     260                 :         19 :         out_plane->texture = *tex;
     261                 :         19 :         out_plane->components = 0;
     262         [ +  + ]:         95 :         for (int i = 0; i < PL_ARRAY_SIZE(out_map); i++) {
     263                 :         76 :             out_plane->component_mapping[i] = out_map[i];
     264         [ +  + ]:         76 :             if (out_map[i] >= 0)
     265                 :         19 :                 out_plane->components = i+1;
     266                 :            :         }
     267                 :            :     }
     268                 :            : 
     269                 :         38 :     struct pl_tex_transfer_params params = {
     270                 :         19 :         .tex        = *tex,
     271                 :         19 :         .rc.x1      = data->width, // set these for `pl_tex_transfer_size`
     272                 :         19 :         .rc.y1      = data->height,
     273                 :            :         .rc.z1      = 1,
     274         [ +  + ]:         19 :         .row_pitch  = PL_DEF(data->row_stride, data->width * fmt->texel_size),
     275                 :         19 :         .ptr        = (void *) data->pixels,
     276                 :         19 :         .buf        = data->buf,
     277                 :         19 :         .buf_offset = data->buf_offset,
     278                 :         19 :         .callback   = data->callback,
     279                 :         19 :         .priv       = data->priv,
     280                 :            :     };
     281                 :            : 
     282                 :         19 :     pl_buf swapbuf = NULL;
     283         [ -  + ]:         19 :     if (data->swapped) {
     284                 :          0 :         const size_t aligned = PL_ALIGN2(pl_tex_transfer_size(&params), 4);
     285                 :          0 :         swapbuf = pl_buf_create(gpu, pl_buf_params(
     286                 :            :             .size           = aligned,
     287                 :            :             .storable       = true,
     288                 :            :             .initial_data   = params.ptr,
     289                 :            : 
     290                 :            :             // Note: This may over-read from `ptr` if `ptr` is not aligned to a
     291                 :            :             // word boundary, but the extra texels will be ignored by
     292                 :            :             // `pl_tex_upload` so this UB should be a non-issue in practice.
     293                 :            :         ));
     294         [ #  # ]:          0 :         if (!swapbuf) {
     295                 :          0 :             PL_ERR(gpu, "Failed creating endian swapping buffer!");
     296                 :          0 :             return false;
     297                 :            :         }
     298                 :            : 
     299                 :          0 :         struct pl_buf_copy_swap_params swap_params = {
     300                 :            :             .src        = swapbuf,
     301                 :            :             .dst        = swapbuf,
     302                 :            :             .size       = aligned,
     303                 :          0 :             .wordsize   = fmt->texel_size / fmt->num_components,
     304                 :            :         };
     305                 :            : 
     306         [ #  # ]:          0 :         bool can_reuse = params.buf && params.buf->params.storable &&
     307   [ #  #  #  # ]:          0 :                          params.buf_offset % 4 == 0 &&
     308         [ #  # ]:          0 :                          params.buf_offset + aligned <= params.buf->params.size;
     309                 :            : 
     310         [ #  # ]:          0 :         if (params.ptr) {
     311                 :            :             // Data is already uploaded (no-op), can swap in-place
     312         [ #  # ]:          0 :         } else if (can_reuse) {
     313                 :            :             // We can sample directly from the source buffer
     314                 :          0 :             swap_params.src = params.buf;
     315                 :          0 :             swap_params.src_offset = params.buf_offset;
     316                 :            :         } else {
     317                 :            :             // We sadly need to do a second memcpy
     318         [ #  # ]:          0 :             assert(params.buf);
     319                 :          0 :             PL_TRACE(gpu, "Double-slow path! pl_buf_copy -> pl_buf_copy_swap...");
     320                 :          0 :             pl_buf_copy(gpu, swapbuf, 0, params.buf, params.buf_offset,
     321                 :          0 :                         PL_MIN(aligned, params.buf->params.size - params.buf_offset));
     322                 :            :         }
     323                 :            : 
     324         [ #  # ]:          0 :         if (!pl_buf_copy_swap(gpu, &swap_params)) {
     325                 :          0 :             PL_ERR(gpu, "Failed swapping endianness!");
     326                 :          0 :             pl_buf_destroy(gpu, &swapbuf);
     327                 :          0 :             return false;
     328                 :            :         }
     329                 :            : 
     330                 :          0 :         params.ptr = NULL;
     331                 :          0 :         params.buf = swapbuf;
     332                 :          0 :         params.buf_offset = 0;
     333                 :            :     }
     334                 :            : 
     335                 :         19 :     ok = pl_tex_upload(gpu, &params);
     336                 :         19 :     pl_buf_destroy(gpu, &swapbuf);
     337                 :         19 :     return ok;
     338                 :            : }
     339                 :            : 
     340                 :          5 : bool pl_recreate_plane(pl_gpu gpu, struct pl_plane *out_plane,
     341                 :            :                        pl_tex *tex, const struct pl_plane_data *data)
     342                 :            : {
     343         [ -  + ]:          5 :     if (data->swapped) {
     344                 :          0 :         PL_ERR(gpu, "Cannot call pl_recreate_plane on non-native endian plane "
     345                 :            :                "data, this is only supported for `pl_upload_plane`!");
     346                 :          0 :         return false;
     347                 :            :     }
     348                 :            : 
     349                 :            :     int out_map[4];
     350                 :          5 :     pl_fmt fmt = pl_plane_find_fmt(gpu, out_map, data);
     351         [ -  + ]:          5 :     if (!fmt) {
     352                 :          0 :         PL_ERR(gpu, "Failed picking any compatible texture format for a plane!");
     353                 :          0 :         return false;
     354                 :            :     }
     355                 :            : 
     356                 :          5 :     bool ok = pl_tex_recreate(gpu, tex, pl_tex_params(
     357                 :            :         .w = data->width,
     358                 :            :         .h = data->height,
     359                 :            :         .format = fmt,
     360                 :            :         .renderable = true,
     361                 :            :         .host_readable = fmt->caps & PL_FMT_CAP_HOST_READABLE,
     362                 :            :         .blit_dst = fmt->caps & PL_FMT_CAP_BLITTABLE,
     363                 :            :         .storable = fmt->caps & PL_FMT_CAP_STORABLE,
     364                 :            :     ));
     365                 :            : 
     366         [ +  + ]:          5 :     if (!ok) {
     367                 :          1 :         PL_ERR(gpu, "Failed initializing plane texture!");
     368                 :          1 :         return false;
     369                 :            :     }
     370                 :            : 
     371         [ -  + ]:          4 :     if (out_plane) {
     372                 :          0 :         out_plane->texture = *tex;
     373                 :          0 :         out_plane->components = 0;
     374         [ #  # ]:          0 :         for (int i = 0; i < PL_ARRAY_SIZE(out_map); i++) {
     375                 :          0 :             out_plane->component_mapping[i] = out_map[i];
     376         [ #  # ]:          0 :             if (out_map[i] >= 0)
     377                 :          0 :                 out_plane->components = i+1;
     378                 :            :         }
     379                 :            :     }
     380                 :            : 
     381                 :            :     return true;
     382                 :            : }

Generated by: LCOV version 1.16