Coverage Report

Created: 2025-08-11 06:44

/src/mpv/video/out/vo_gpu_next.c
Line
Count
Source (jump to first uncovered line)
1
/*
2
 * Copyright (C) 2021 Niklas Haas
3
 *
4
 * This file is part of mpv.
5
 *
6
 * mpv is free software; you can redistribute it and/or
7
 * modify it under the terms of the GNU Lesser General Public
8
 * License as published by the Free Software Foundation; either
9
 * version 2.1 of the License, or (at your option) any later version.
10
 *
11
 * mpv is distributed in the hope that it will be useful,
12
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
 * GNU Lesser General Public License for more details.
15
 *
16
 * You should have received a copy of the GNU Lesser General Public
17
 * License along with mpv.  If not, see <http://www.gnu.org/licenses/>.
18
 */
19
20
#include <sys/stat.h>
21
#include <time.h>
22
23
#include <libplacebo/colorspace.h>
24
#include <libplacebo/options.h>
25
#include <libplacebo/renderer.h>
26
#include <libplacebo/shaders/lut.h>
27
#include <libplacebo/shaders/icc.h>
28
#include <libplacebo/utils/libav.h>
29
#include <libplacebo/utils/frame_queue.h>
30
31
#include "config.h"
32
#include "common/common.h"
33
#include "misc/io_utils.h"
34
#include "options/m_config.h"
35
#include "options/options.h"
36
#include "options/path.h"
37
#include "osdep/io.h"
38
#include "osdep/threads.h"
39
#include "stream/stream.h"
40
#include "sub/draw_bmp.h"
41
#include "video/fmt-conversion.h"
42
#include "video/mp_image.h"
43
#include "video/out/placebo/ra_pl.h"
44
#include "placebo/utils.h"
45
#include "gpu/context.h"
46
#include "gpu/hwdec.h"
47
#include "gpu/video.h"
48
#include "gpu/video_shaders.h"
49
#include "sub/osd.h"
50
#include "gpu_next/context.h"
51
52
#if HAVE_GL && defined(PL_HAVE_OPENGL)
53
#include <libplacebo/opengl.h>
54
#include "video/out/opengl/ra_gl.h"
55
#endif
56
57
#if HAVE_D3D11 && defined(PL_HAVE_D3D11)
58
#include <libplacebo/d3d11.h>
59
#include "video/out/d3d11/ra_d3d11.h"
60
#include "osdep/windows_utils.h"
61
#endif
62
63
64
struct osd_entry {
65
    pl_tex tex;
66
    struct pl_overlay_part *parts;
67
    int num_parts;
68
};
69
70
struct osd_state {
71
    struct osd_entry entries[MAX_OSD_PARTS];
72
    struct pl_overlay overlays[MAX_OSD_PARTS];
73
};
74
75
struct scaler_params {
76
    struct pl_filter_config config;
77
};
78
79
struct user_hook {
80
    char *path;
81
    const struct pl_hook *hook;
82
};
83
84
struct user_lut {
85
    char *opt;
86
    char *path;
87
    int type;
88
    struct pl_custom_lut *lut;
89
};
90
91
struct frame_info {
92
    int count;
93
    struct pl_dispatch_info info[VO_PASS_PERF_MAX];
94
};
95
96
struct cache {
97
    struct mp_log *log;
98
    struct mpv_global *global;
99
    char *dir;
100
    const char *name;
101
    size_t size_limit;
102
    pl_cache cache;
103
};
104
105
struct priv {
106
    struct mp_log *log;
107
    struct mpv_global *global;
108
    struct ra_ctx *ra_ctx;
109
    struct gpu_ctx *context;
110
    struct ra_hwdec_ctx hwdec_ctx;
111
    struct ra_hwdec_mapper *hwdec_mapper;
112
113
    // Allocated DR buffers
114
    mp_mutex dr_lock;
115
    pl_buf *dr_buffers;
116
    int num_dr_buffers;
117
118
    pl_log pllog;
119
    pl_gpu gpu;
120
    pl_renderer rr;
121
    pl_queue queue;
122
    pl_swapchain sw;
123
    pl_fmt osd_fmt[SUBBITMAP_COUNT];
124
    pl_tex *sub_tex;
125
    int num_sub_tex;
126
127
    struct mp_rect src, dst;
128
    struct mp_osd_res osd_res;
129
    struct osd_state osd_state;
130
131
    uint64_t last_id;
132
    uint64_t osd_sync;
133
    double last_pts;
134
    bool is_interpolated;
135
    bool want_reset;
136
    bool frame_pending;
137
138
    pl_options pars;
139
    struct m_config_cache *opts_cache;
140
    struct m_config_cache *next_opts_cache;
141
    struct gl_next_opts *next_opts;
142
    struct cache shader_cache, icc_cache;
143
    struct mp_csp_equalizer_state *video_eq;
144
    struct scaler_params scalers[SCALER_COUNT];
145
    const struct pl_hook **hooks; // storage for `params.hooks`
146
    enum pl_color_levels output_levels;
147
148
    struct pl_icc_params icc_params;
149
    char *icc_path;
150
    pl_icc_object icc_profile;
151
152
    // Cached shaders, preserved across options updates
153
    struct user_hook *user_hooks;
154
    int num_user_hooks;
155
156
    // Performance data of last frame
157
    struct frame_info perf_fresh;
158
    struct frame_info perf_redraw;
159
160
    struct mp_image_params target_params;
161
};
162
163
static void update_render_options(struct vo *vo);
164
static void update_lut(struct priv *p, struct user_lut *lut);
165
166
struct gl_next_opts {
167
    bool delayed_peak;
168
    int sub_hdr_peak;
169
    int image_subs_hdr_peak;
170
    int border_background;
171
    float corner_rounding;
172
    bool inter_preserve;
173
    struct user_lut lut;
174
    struct user_lut image_lut;
175
    struct user_lut target_lut;
176
    int target_hint;
177
    int target_hint_mode;
178
    char **raw_opts;
179
};
180
181
const struct m_opt_choice_alternatives lut_types[] = {
182
    {"auto",        PL_LUT_UNKNOWN},
183
    {"native",      PL_LUT_NATIVE},
184
    {"normalized",  PL_LUT_NORMALIZED},
185
    {"conversion",  PL_LUT_CONVERSION},
186
    {0}
187
};
188
189
#define OPT_BASE_STRUCT struct gl_next_opts
190
const struct m_sub_options gl_next_conf = {
191
    .opts = (const struct m_option[]) {
192
        {"sub-hdr-peak", OPT_CHOICE(sub_hdr_peak, {"sdr", PL_COLOR_SDR_WHITE}),
193
            M_RANGE(10, 10000)},
194
        {"image-subs-hdr-peak", OPT_CHOICE(image_subs_hdr_peak, {"sdr", PL_COLOR_SDR_WHITE},
195
            {"video", -1}),  M_RANGE(10, 10000)},
196
        {"allow-delayed-peak-detect", OPT_BOOL(delayed_peak)},
197
        {"border-background", OPT_CHOICE(border_background,
198
            {"none",  BACKGROUND_NONE},
199
            {"color", BACKGROUND_COLOR},
200
            {"tiles", BACKGROUND_TILES})},
201
        {"corner-rounding", OPT_FLOAT(corner_rounding), M_RANGE(0, 1)},
202
        {"interpolation-preserve", OPT_BOOL(inter_preserve)},
203
        {"lut", OPT_STRING(lut.opt), .flags = M_OPT_FILE},
204
        {"lut-type", OPT_CHOICE_C(lut.type, lut_types)},
205
        {"image-lut", OPT_STRING(image_lut.opt), .flags = M_OPT_FILE},
206
        {"image-lut-type", OPT_CHOICE_C(image_lut.type, lut_types)},
207
        {"target-lut", OPT_STRING(target_lut.opt), .flags = M_OPT_FILE},
208
        {"target-colorspace-hint", OPT_CHOICE(target_hint, {"auto", -1}, {"no", 0}, {"yes", 1})},
209
        {"target-colorspace-hint-mode", OPT_CHOICE(target_hint_mode, {"target", 0}, {"source", 1}, {"source-dynamic", 2})},
210
        // No `target-lut-type` because we don't support non-RGB targets
211
        {"libplacebo-opts", OPT_KEYVALUELIST(raw_opts)},
212
        {0},
213
    },
214
    .defaults = &(struct gl_next_opts) {
215
        .border_background = BACKGROUND_COLOR,
216
        .inter_preserve = true,
217
        .sub_hdr_peak = PL_COLOR_SDR_WHITE,
218
        .image_subs_hdr_peak = PL_COLOR_SDR_WHITE,
219
        .target_hint = -1,
220
    },
221
    .size = sizeof(struct gl_next_opts),
222
    .change_flags = UPDATE_VIDEO,
223
};
224
225
static pl_buf get_dr_buf(struct priv *p, const uint8_t *ptr)
226
0
{
227
0
    mp_mutex_lock(&p->dr_lock);
228
229
0
    for (int i = 0; i < p->num_dr_buffers; i++) {
230
0
        pl_buf buf = p->dr_buffers[i];
231
0
        if (ptr >= buf->data && ptr < buf->data + buf->params.size) {
232
0
            mp_mutex_unlock(&p->dr_lock);
233
0
            return buf;
234
0
        }
235
0
    }
236
237
0
    mp_mutex_unlock(&p->dr_lock);
238
0
    return NULL;
239
0
}
240
241
static void free_dr_buf(void *opaque, uint8_t *data)
242
0
{
243
0
    struct priv *p = opaque;
244
0
    mp_mutex_lock(&p->dr_lock);
245
246
0
    for (int i = 0; i < p->num_dr_buffers; i++) {
247
0
        if (p->dr_buffers[i]->data == data) {
248
0
            pl_buf_destroy(p->gpu, &p->dr_buffers[i]);
249
0
            MP_TARRAY_REMOVE_AT(p->dr_buffers, p->num_dr_buffers, i);
250
0
            mp_mutex_unlock(&p->dr_lock);
251
0
            return;
252
0
        }
253
0
    }
254
255
0
    MP_ASSERT_UNREACHABLE();
256
0
}
257
258
static struct mp_image *get_image(struct vo *vo, int imgfmt, int w, int h,
259
                                  int stride_align, int flags)
260
0
{
261
0
    struct priv *p = vo->priv;
262
0
    pl_gpu gpu = p->gpu;
263
0
    if (!gpu->limits.thread_safe || !gpu->limits.max_mapped_size)
264
0
        return NULL;
265
266
0
    if ((flags & VO_DR_FLAG_HOST_CACHED) && !gpu->limits.host_cached)
267
0
        return NULL;
268
269
0
    stride_align = mp_lcm(stride_align, gpu->limits.align_tex_xfer_pitch);
270
0
    stride_align = mp_lcm(stride_align, gpu->limits.align_tex_xfer_offset);
271
0
    int size = mp_image_get_alloc_size(imgfmt, w, h, stride_align);
272
0
    if (size < 0)
273
0
        return NULL;
274
275
0
    pl_buf buf = pl_buf_create(gpu, &(struct pl_buf_params) {
276
0
        .memory_type = PL_BUF_MEM_HOST,
277
0
        .host_mapped = true,
278
0
        .size = size + stride_align,
279
0
    });
280
281
0
    if (!buf)
282
0
        return NULL;
283
284
0
    struct mp_image *mpi = mp_image_from_buffer(imgfmt, w, h, stride_align,
285
0
                                                buf->data, buf->params.size,
286
0
                                                p, free_dr_buf);
287
0
    if (!mpi) {
288
0
        pl_buf_destroy(gpu, &buf);
289
0
        return NULL;
290
0
    }
291
292
0
    mp_mutex_lock(&p->dr_lock);
293
0
    MP_TARRAY_APPEND(p, p->dr_buffers, p->num_dr_buffers, buf);
294
0
    mp_mutex_unlock(&p->dr_lock);
295
296
0
    return mpi;
297
0
}
298
299
static void update_overlays(struct vo *vo, struct mp_osd_res res,
300
                            int flags, enum pl_overlay_coords coords,
301
                            struct osd_state *state, struct pl_frame *frame,
302
                            struct mp_image *src)
303
0
{
304
0
    struct priv *p = vo->priv;
305
0
    double pts = src ? src->pts : 0;
306
0
    struct sub_bitmap_list *subs = osd_render(vo->osd, res, pts, flags, mp_draw_sub_formats);
307
308
0
    frame->overlays = state->overlays;
309
0
    frame->num_overlays = 0;
310
311
0
    for (int n = 0; n < subs->num_items; n++) {
312
0
        const struct sub_bitmaps *item = subs->items[n];
313
0
        if (!item->num_parts || !item->packed)
314
0
            continue;
315
0
        struct osd_entry *entry = &state->entries[item->render_index];
316
0
        pl_fmt tex_fmt = p->osd_fmt[item->format];
317
0
        if (!entry->tex)
318
0
            MP_TARRAY_POP(p->sub_tex, p->num_sub_tex, &entry->tex);
319
0
        bool ok = pl_tex_recreate(p->gpu, &entry->tex, &(struct pl_tex_params) {
320
0
            .format = tex_fmt,
321
0
            .w = MPMAX(item->packed_w, entry->tex ? entry->tex->params.w : 0),
322
0
            .h = MPMAX(item->packed_h, entry->tex ? entry->tex->params.h : 0),
323
0
            .host_writable = true,
324
0
            .sampleable = true,
325
0
        });
326
0
        if (!ok) {
327
0
            MP_ERR(vo, "Failed recreating OSD texture!\n");
328
0
            break;
329
0
        }
330
0
        ok = pl_tex_upload(p->gpu, &(struct pl_tex_transfer_params) {
331
0
            .tex        = entry->tex,
332
0
            .rc         = { .x1 = item->packed_w, .y1 = item->packed_h, },
333
0
            .row_pitch  = item->packed->stride[0],
334
0
            .ptr        = item->packed->planes[0],
335
0
        });
336
0
        if (!ok) {
337
0
            MP_ERR(vo, "Failed uploading OSD texture!\n");
338
0
            break;
339
0
        }
340
341
0
        entry->num_parts = 0;
342
0
        for (int i = 0; i < item->num_parts; i++) {
343
0
            const struct sub_bitmap *b = &item->parts[i];
344
0
            if (b->dw == 0 || b->dh == 0)
345
0
                continue;
346
0
            uint32_t c = b->libass.color;
347
0
            struct pl_overlay_part part = {
348
0
                .src = { b->src_x, b->src_y, b->src_x + b->w, b->src_y + b->h },
349
0
                .dst = { b->x, b->y, b->x + b->dw, b->y + b->dh },
350
0
                .color = {
351
0
                    (c >> 24) / 255.0f,
352
0
                    ((c >> 16) & 0xFF) / 255.0f,
353
0
                    ((c >> 8) & 0xFF) / 255.0f,
354
0
                    (255 - (c & 0xFF)) / 255.0f,
355
0
                }
356
0
            };
357
0
            MP_TARRAY_APPEND(p, entry->parts, entry->num_parts, part);
358
0
        }
359
360
0
        struct pl_overlay *ol = &state->overlays[frame->num_overlays++];
361
0
        *ol = (struct pl_overlay) {
362
0
            .tex = entry->tex,
363
0
            .parts = entry->parts,
364
0
            .num_parts = entry->num_parts,
365
0
            .color = {
366
0
                .primaries = PL_COLOR_PRIM_BT_709,
367
0
                .transfer = PL_COLOR_TRC_SRGB,
368
0
            },
369
0
            .coords = coords,
370
0
        };
371
372
0
        switch (item->format) {
373
0
        case SUBBITMAP_BGRA:
374
0
            ol->mode = PL_OVERLAY_NORMAL;
375
0
            ol->repr.alpha = PL_ALPHA_PREMULTIPLIED;
376
            // Infer bitmap colorspace from source
377
0
            if (src) {
378
0
                ol->color = src->params.color;
379
0
                if (pl_color_transfer_is_hdr(ol->color.transfer)) {
380
0
                    if (!pl_color_transfer_is_hdr(frame->color.transfer)) {
381
                        // Tone mapping targets SDR white
382
0
                        ol->color.hdr = (struct pl_hdr_metadata) {
383
0
                            .max_luma = PL_COLOR_SDR_WHITE,
384
0
                        };
385
0
                    } else if (p->next_opts->image_subs_hdr_peak != -1) {
386
0
                        ol->color.hdr = (struct pl_hdr_metadata) {
387
0
                            .max_luma = p->next_opts->image_subs_hdr_peak,
388
0
                        };
389
0
                    }
390
0
                }
391
0
            }
392
0
            break;
393
0
        case SUBBITMAP_LIBASS:
394
0
            if (src && item->video_color_space && !pl_color_space_is_hdr(&src->params.color))
395
0
                ol->color = src->params.color;
396
0
            if (src && pl_color_transfer_is_hdr(frame->color.transfer)) {
397
0
                ol->color.hdr = (struct pl_hdr_metadata) {
398
0
                    .max_luma = p->next_opts->sub_hdr_peak,
399
0
                };
400
0
            }
401
0
            ol->mode = PL_OVERLAY_MONOCHROME;
402
0
            ol->repr.alpha = PL_ALPHA_INDEPENDENT;
403
0
            break;
404
0
        }
405
0
    }
406
407
0
    talloc_free(subs);
408
0
}
409
410
struct frame_priv {
411
    struct vo *vo;
412
    struct osd_state subs;
413
    uint64_t osd_sync;
414
    struct ra_hwdec *hwdec;
415
};
416
417
static int plane_data_from_imgfmt(struct pl_plane_data out_data[4],
418
                                  struct pl_bit_encoding *out_bits,
419
                                  enum mp_imgfmt imgfmt, bool use_uint)
420
0
{
421
0
    struct mp_imgfmt_desc desc = mp_imgfmt_get_desc(imgfmt);
422
0
    if (!desc.num_planes || !(desc.flags & MP_IMGFLAG_HAS_COMPS))
423
0
        return 0;
424
425
0
    if (desc.flags & MP_IMGFLAG_HWACCEL)
426
0
        return 0; // HW-accelerated frames need to be mapped differently
427
428
0
    if (!(desc.flags & MP_IMGFLAG_NE))
429
0
        return 0; // GPU endianness follows the host's
430
431
0
    if (desc.flags & MP_IMGFLAG_PAL)
432
0
        return 0; // Palette formats (currently) not supported in libplacebo
433
434
0
    if ((desc.flags & MP_IMGFLAG_TYPE_FLOAT) && (desc.flags & MP_IMGFLAG_YUV))
435
0
        return 0; // Floating-point YUV (currently) unsupported
436
437
0
    bool has_bits = false;
438
0
    bool any_padded = false;
439
440
0
    for (int p = 0; p < desc.num_planes; p++) {
441
0
        struct pl_plane_data *data = &out_data[p];
442
0
        struct mp_imgfmt_comp_desc sorted[MP_NUM_COMPONENTS];
443
0
        int num_comps = 0;
444
0
        if (desc.bpp[p] % 8)
445
0
            return 0; // Pixel size is not byte-aligned
446
447
0
        for (int c = 0; c < mp_imgfmt_desc_get_num_comps(&desc); c++) {
448
0
            if (desc.comps[c].plane != p)
449
0
                continue;
450
451
0
            data->component_map[num_comps] = c;
452
0
            sorted[num_comps] = desc.comps[c];
453
0
            num_comps++;
454
455
            // Sort components by offset order, while keeping track of the
456
            // semantic mapping in `data->component_map`
457
0
            for (int i = num_comps - 1; i > 0; i--) {
458
0
                if (sorted[i].offset >= sorted[i - 1].offset)
459
0
                    break;
460
0
                MPSWAP(struct mp_imgfmt_comp_desc, sorted[i], sorted[i - 1]);
461
0
                MPSWAP(int, data->component_map[i], data->component_map[i - 1]);
462
0
            }
463
0
        }
464
465
0
        uint64_t total_bits = 0;
466
467
        // Fill in the pl_plane_data fields for each component
468
0
        memset(data->component_size, 0, sizeof(data->component_size));
469
0
        for (int c = 0; c < num_comps; c++) {
470
0
            data->component_size[c] = sorted[c].size;
471
0
            data->component_pad[c] = sorted[c].offset - total_bits;
472
0
            total_bits += data->component_pad[c] + data->component_size[c];
473
0
            any_padded |= sorted[c].pad;
474
475
            // Ignore bit encoding of alpha channel
476
0
            if (!out_bits || data->component_map[c] == PL_CHANNEL_A)
477
0
                continue;
478
479
0
            struct pl_bit_encoding bits = {
480
0
                .sample_depth = data->component_size[c],
481
0
                .color_depth = sorted[c].size - abs(sorted[c].pad),
482
0
                .bit_shift = MPMAX(sorted[c].pad, 0),
483
0
            };
484
485
0
            if (!has_bits) {
486
0
                *out_bits = bits;
487
0
                has_bits = true;
488
0
            } else {
489
0
                if (!pl_bit_encoding_equal(out_bits, &bits)) {
490
                    // Bit encoding differs between components/planes,
491
                    // cannot handle this
492
0
                    *out_bits = (struct pl_bit_encoding) {0};
493
0
                    out_bits = NULL;
494
0
                }
495
0
            }
496
0
        }
497
498
0
        data->pixel_stride = desc.bpp[p] / 8;
499
0
        data->type = (desc.flags & MP_IMGFLAG_TYPE_FLOAT)
500
0
                            ? PL_FMT_FLOAT
501
0
                            : (use_uint ? PL_FMT_UINT : PL_FMT_UNORM);
502
0
    }
503
504
0
    if (any_padded && !out_bits)
505
0
        return 0; // can't handle padded components without `pl_bit_encoding`
506
507
0
    return desc.num_planes;
508
0
}
509
510
static bool hwdec_reconfig(struct priv *p, struct ra_hwdec *hwdec,
511
                           const struct mp_image_params *par)
512
0
{
513
0
    if (p->hwdec_mapper) {
514
0
        if (mp_image_params_static_equal(par, &p->hwdec_mapper->src_params)) {
515
0
            p->hwdec_mapper->src_params.repr.dovi = par->repr.dovi;
516
0
            p->hwdec_mapper->dst_params.repr.dovi = par->repr.dovi;
517
0
            p->hwdec_mapper->src_params.color.hdr = par->color.hdr;
518
0
            p->hwdec_mapper->dst_params.color.hdr = par->color.hdr;
519
0
            return p->hwdec_mapper;
520
0
        } else {
521
0
            ra_hwdec_mapper_free(&p->hwdec_mapper);
522
0
        }
523
0
    }
524
525
0
    p->hwdec_mapper = ra_hwdec_mapper_create(hwdec, par);
526
0
    if (!p->hwdec_mapper) {
527
0
        MP_ERR(p, "Initializing texture for hardware decoding failed.\n");
528
0
        return NULL;
529
0
    }
530
531
0
    return p->hwdec_mapper;
532
0
}
533
534
// For RAs not based on ra_pl, this creates a new pl_tex wrapper
535
static pl_tex hwdec_get_tex(struct priv *p, int n)
536
0
{
537
0
    struct ra_tex *ratex = p->hwdec_mapper->tex[n];
538
0
    struct ra *ra = p->hwdec_mapper->ra;
539
0
    if (ra_pl_get(ra))
540
0
        return (pl_tex) ratex->priv;
541
542
0
#if HAVE_GL && defined(PL_HAVE_OPENGL)
543
0
    if (ra_is_gl(ra) && pl_opengl_get(p->gpu)) {
544
0
        struct pl_opengl_wrap_params par = {
545
0
            .width = ratex->params.w,
546
0
            .height = ratex->params.h,
547
0
        };
548
549
0
        ra_gl_get_format(ratex->params.format, &par.iformat,
550
0
                         &(GLenum){0}, &(GLenum){0});
551
0
        ra_gl_get_raw_tex(ra, ratex, &par.texture, &par.target);
552
0
        return pl_opengl_wrap(p->gpu, &par);
553
0
    }
554
0
#endif
555
556
#if HAVE_D3D11 && defined(PL_HAVE_D3D11)
557
    if (ra_is_d3d11(ra)) {
558
        int array_slice = 0;
559
        ID3D11Resource *res = ra_d3d11_get_raw_tex(ra, ratex, &array_slice);
560
        pl_tex tex = pl_d3d11_wrap(p->gpu, pl_d3d11_wrap_params(
561
            .tex = res,
562
            .array_slice = array_slice,
563
            .fmt = ra_d3d11_get_format(ratex->params.format),
564
            .w = ratex->params.w,
565
            .h = ratex->params.h,
566
        ));
567
        SAFE_RELEASE(res);
568
        return tex;
569
    }
570
#endif
571
572
0
    MP_ERR(p, "Failed mapping hwdec frame? Open a bug!\n");
573
0
    return false;
574
0
}
575
576
static bool hwdec_acquire(pl_gpu gpu, struct pl_frame *frame)
577
0
{
578
0
    struct mp_image *mpi = frame->user_data;
579
0
    struct frame_priv *fp = mpi->priv;
580
0
    struct priv *p = fp->vo->priv;
581
0
    if (!hwdec_reconfig(p, fp->hwdec, &mpi->params))
582
0
        return false;
583
584
0
    if (ra_hwdec_mapper_map(p->hwdec_mapper, mpi) < 0) {
585
0
        MP_ERR(p, "Mapping hardware decoded surface failed.\n");
586
0
        return false;
587
0
    }
588
589
0
    for (int n = 0; n < frame->num_planes; n++) {
590
0
        if (!(frame->planes[n].texture = hwdec_get_tex(p, n)))
591
0
            return false;
592
0
    }
593
594
0
    return true;
595
0
}
596
597
static void hwdec_release(pl_gpu gpu, struct pl_frame *frame)
598
0
{
599
0
    struct mp_image *mpi = frame->user_data;
600
0
    struct frame_priv *fp = mpi->priv;
601
0
    struct priv *p = fp->vo->priv;
602
0
    if (!ra_pl_get(p->hwdec_mapper->ra)) {
603
0
        for (int n = 0; n < frame->num_planes; n++)
604
0
            pl_tex_destroy(p->gpu, &frame->planes[n].texture);
605
0
    }
606
607
0
    ra_hwdec_mapper_unmap(p->hwdec_mapper);
608
0
}
609
610
static bool format_supported(struct vo *vo, int format, bool use_uint)
611
0
{
612
0
    struct priv *p = vo->priv;
613
0
    struct pl_bit_encoding bits;
614
0
    struct pl_plane_data data[4] = {0};
615
0
    int planes = plane_data_from_imgfmt(data, &bits, format, use_uint);
616
0
    if (!planes)
617
0
        return false;
618
619
0
    for (int i = 0; i < planes; i++) {
620
0
        if (!pl_plane_find_fmt(p->gpu, NULL, &data[i]))
621
0
            return false;
622
0
    }
623
624
0
    return true;
625
0
}
626
627
static bool map_frame(pl_gpu gpu, pl_tex *tex, const struct pl_source_frame *src,
628
                      struct pl_frame *frame)
629
0
{
630
0
    struct mp_image *mpi = src->frame_data;
631
0
    struct mp_image_params par = mpi->params;
632
0
    struct frame_priv *fp = mpi->priv;
633
0
    struct vo *vo = fp->vo;
634
0
    struct priv *p = vo->priv;
635
636
0
    fp->hwdec = ra_hwdec_get(&p->hwdec_ctx, mpi->imgfmt);
637
0
    if (fp->hwdec) {
638
        // Note: We don't actually need the mapper to map the frame yet, we
639
        // only reconfig the mapper here (potentially creating it) to access
640
        // `dst_params`. In practice, though, this should not matter unless the
641
        // image format changes mid-stream.
642
0
        if (!hwdec_reconfig(p, fp->hwdec, &mpi->params)) {
643
0
            talloc_free(mpi);
644
0
            return false;
645
0
        }
646
647
0
        par = p->hwdec_mapper->dst_params;
648
0
    }
649
650
0
    mp_image_params_guess_csp(&par);
651
652
0
    *frame = (struct pl_frame) {
653
0
        .color = par.color,
654
0
        .repr = par.repr,
655
0
        .profile = {
656
0
            .data = mpi->icc_profile ? mpi->icc_profile->data : NULL,
657
0
            .len = mpi->icc_profile ? mpi->icc_profile->size : 0,
658
0
        },
659
0
        .rotation = par.rotate / 90,
660
0
        .user_data = mpi,
661
0
    };
662
663
0
    if (fp->hwdec) {
664
665
0
        struct mp_imgfmt_desc desc = mp_imgfmt_get_desc(par.imgfmt);
666
0
        frame->acquire = hwdec_acquire;
667
0
        frame->release = hwdec_release;
668
0
        frame->num_planes = desc.num_planes;
669
0
        for (int n = 0; n < frame->num_planes; n++) {
670
0
            struct pl_plane *plane = &frame->planes[n];
671
0
            int *map = plane->component_mapping;
672
0
            for (int c = 0; c < mp_imgfmt_desc_get_num_comps(&desc); c++) {
673
0
                if (desc.comps[c].plane != n)
674
0
                    continue;
675
676
                // Sort by component offset
677
0
                uint8_t offset = desc.comps[c].offset;
678
0
                int index = plane->components++;
679
0
                while (index > 0 && desc.comps[map[index - 1]].offset > offset) {
680
0
                    map[index] = map[index - 1];
681
0
                    index--;
682
0
                }
683
0
                map[index] = c;
684
0
            }
685
0
        }
686
687
0
    } else { // swdec
688
689
0
        struct pl_plane_data data[4] = {0};
690
0
        bool use_uint = false;
691
692
        // At this point, we know that the format is supported, query_format()
693
        // makes sure of that. Just check if we should use UINT as a fallback.
694
0
        if (!format_supported(vo, mpi->imgfmt, false))
695
0
            use_uint = true;
696
697
0
        frame->num_planes = plane_data_from_imgfmt(data, &frame->repr.bits, mpi->imgfmt, use_uint);
698
0
        for (int n = 0; n < frame->num_planes; n++) {
699
0
            struct pl_plane *plane = &frame->planes[n];
700
0
            data[n].width = mp_image_plane_w(mpi, n);
701
0
            data[n].height = mp_image_plane_h(mpi, n);
702
0
            if (mpi->stride[n] < 0) {
703
0
                data[n].pixels = mpi->planes[n] + (data[n].height - 1) * mpi->stride[n];
704
0
                data[n].row_stride = -mpi->stride[n];
705
0
                plane->flipped = true;
706
0
            } else {
707
0
                data[n].pixels = mpi->planes[n];
708
0
                data[n].row_stride = mpi->stride[n];
709
0
            }
710
711
0
            pl_buf buf = get_dr_buf(p, data[n].pixels);
712
0
            if (buf) {
713
0
                data[n].buf = buf;
714
0
                data[n].buf_offset = (uint8_t *) data[n].pixels - buf->data;
715
0
                data[n].pixels = NULL;
716
0
            } else if (gpu->limits.callbacks) {
717
0
                data[n].callback = talloc_free;
718
0
                data[n].priv = mp_image_new_ref(mpi);
719
0
            }
720
721
0
            if (!pl_upload_plane(gpu, plane, &tex[n], &data[n])) {
722
0
                MP_ERR(vo, "Failed uploading frame!\n");
723
0
                talloc_free(data[n].priv);
724
0
                talloc_free(mpi);
725
0
                return false;
726
0
            }
727
0
        }
728
729
0
    }
730
731
    // Update chroma location, must be done after initializing planes
732
0
    pl_frame_set_chroma_location(frame, par.chroma_location);
733
734
0
    if (mpi->film_grain)
735
0
        pl_film_grain_from_av(&frame->film_grain, (AVFilmGrainParams *) mpi->film_grain->data);
736
737
    // Compute a unique signature for any attached ICC profile. Wasteful in
738
    // theory if the ICC profile is the same for multiple frames, but in
739
    // practice ICC profiles are overwhelmingly going to be attached to
740
    // still images so it shouldn't matter.
741
0
    pl_icc_profile_compute_signature(&frame->profile);
742
743
    // Update LUT attached to this frame
744
0
    update_lut(p, &p->next_opts->image_lut);
745
0
    frame->lut = p->next_opts->image_lut.lut;
746
0
    frame->lut_type = p->next_opts->image_lut.type;
747
0
    return true;
748
0
}
749
750
static void unmap_frame(pl_gpu gpu, struct pl_frame *frame,
751
                        const struct pl_source_frame *src)
752
0
{
753
0
    struct mp_image *mpi = src->frame_data;
754
0
    struct frame_priv *fp = mpi->priv;
755
0
    struct priv *p = fp->vo->priv;
756
0
    for (int i = 0; i < MP_ARRAY_SIZE(fp->subs.entries); i++) {
757
0
        pl_tex tex = fp->subs.entries[i].tex;
758
0
        if (tex)
759
0
            MP_TARRAY_APPEND(p, p->sub_tex, p->num_sub_tex, tex);
760
0
    }
761
0
    talloc_free(mpi);
762
0
}
763
764
static void discard_frame(const struct pl_source_frame *src)
765
0
{
766
0
    struct mp_image *mpi = src->frame_data;
767
0
    talloc_free(mpi);
768
0
}
769
770
static void info_callback(void *priv, const struct pl_render_info *info)
771
0
{
772
0
    struct vo *vo = priv;
773
0
    struct priv *p = vo->priv;
774
0
    if (info->index >= VO_PASS_PERF_MAX)
775
0
        return; // silently ignore clipped passes, whatever
776
777
0
    struct frame_info *frame;
778
0
    switch (info->stage) {
779
0
    case PL_RENDER_STAGE_FRAME: frame = &p->perf_fresh; break;
780
0
    case PL_RENDER_STAGE_BLEND: frame = &p->perf_redraw; break;
781
0
    default: abort();
782
0
    }
783
784
0
    frame->count = info->index + 1;
785
0
    pl_dispatch_info_move(&frame->info[info->index], info->pass);
786
0
}
787
788
static void update_options(struct vo *vo)
789
0
{
790
0
    struct priv *p = vo->priv;
791
0
    pl_options pars = p->pars;
792
0
    bool changed = m_config_cache_update(p->opts_cache);
793
0
    changed = m_config_cache_update(p->next_opts_cache) || changed;
794
0
    if (changed)
795
0
        update_render_options(vo);
796
797
0
    update_lut(p, &p->next_opts->lut);
798
0
    pars->params.lut = p->next_opts->lut.lut;
799
0
    pars->params.lut_type = p->next_opts->lut.type;
800
801
    // Update equalizer state
802
0
    struct mp_csp_params cparams = MP_CSP_PARAMS_DEFAULTS;
803
0
    const struct gl_video_opts *opts = p->opts_cache->opts;
804
0
    mp_csp_equalizer_state_get(p->video_eq, &cparams);
805
0
    pars->color_adjustment.brightness = cparams.brightness;
806
0
    pars->color_adjustment.contrast = cparams.contrast;
807
0
    pars->color_adjustment.hue = cparams.hue;
808
0
    pars->color_adjustment.saturation = cparams.saturation;
809
0
    pars->color_adjustment.gamma = cparams.gamma * opts->gamma;
810
0
    p->output_levels = cparams.levels_out;
811
812
0
    for (char **kv = p->next_opts->raw_opts; kv && kv[0]; kv += 2)
813
0
        pl_options_set_str(pars, kv[0], kv[1]);
814
0
}
815
816
static void apply_target_contrast(struct priv *p, struct pl_color_space *color, float min_luma)
817
0
{
818
0
    const struct gl_video_opts *opts = p->opts_cache->opts;
819
820
    // Auto mode, use target value if available
821
0
    if (!opts->target_contrast) {
822
0
        color->hdr.min_luma = min_luma;
823
0
        return;
824
0
    }
825
826
    // Infinite contrast
827
0
    if (opts->target_contrast == -1) {
828
0
        color->hdr.min_luma = 1e-7;
829
0
        return;
830
0
    }
831
832
    // Infer max_luma for current pl_color_space
833
0
    pl_color_space_nominal_luma_ex(pl_nominal_luma_params(
834
0
        .color = color,
835
        // with HDR10 meta to respect value if already set
836
0
        .metadata = PL_HDR_METADATA_HDR10,
837
0
        .scaling = PL_HDR_NITS,
838
0
        .out_max = &color->hdr.max_luma
839
0
    ));
840
841
0
    color->hdr.min_luma = color->hdr.max_luma / opts->target_contrast;
842
0
}
843
844
static void apply_target_options(struct priv *p, struct pl_frame *target,
845
                                 float min_luma, bool hint)
846
0
{
847
0
    update_lut(p, &p->next_opts->target_lut);
848
0
    target->lut = p->next_opts->target_lut.lut;
849
0
    target->lut_type = p->next_opts->target_lut.type;
850
851
    // Colorspace overrides
852
0
    const struct gl_video_opts *opts = p->opts_cache->opts;
853
    // If swapchain returned a value use this, override is used in hint
854
0
    if (p->output_levels)
855
0
        target->repr.levels = p->output_levels;
856
0
    if (opts->target_prim && (!target->color.primaries || !hint))
857
0
        target->color.primaries = opts->target_prim;
858
0
    if (opts->target_trc && (!target->color.transfer || !hint))
859
0
        target->color.transfer = opts->target_trc;
860
0
    if (opts->target_peak && (!target->color.hdr.max_luma || !hint))
861
0
        target->color.hdr.max_luma = opts->target_peak;
862
0
    if ((!target->color.hdr.min_luma || !hint))
863
0
        apply_target_contrast(p, &target->color, min_luma);
864
0
    if (opts->target_gamut) {
865
        // Ensure resulting gamut still fits inside container
866
0
        const struct pl_raw_primaries *gamut, *container;
867
0
        gamut = pl_raw_primaries_get(opts->target_gamut);
868
0
        container = pl_raw_primaries_get(target->color.primaries);
869
0
        target->color.hdr.prim = pl_primaries_clip(gamut, container);
870
0
    }
871
0
    int dither_depth = opts->dither_depth;
872
0
    if (dither_depth == 0) {
873
0
        struct ra_swapchain *sw = p->ra_ctx->swapchain;
874
0
        if (sw->fns->color_depth && sw->fns->color_depth(sw) != -1) {
875
0
            dither_depth = sw->fns->color_depth(sw);
876
0
        } else if (!pl_color_transfer_is_hdr(target->color.transfer)) {
877
0
            dither_depth = 8;
878
0
        }
879
0
    }
880
0
    if (dither_depth > 0) {
881
0
        struct pl_bit_encoding *tbits = &target->repr.bits;
882
0
        tbits->color_depth += dither_depth - tbits->sample_depth;
883
0
        tbits->sample_depth = dither_depth;
884
0
    }
885
886
0
    if (opts->icc_opts->icc_use_luma) {
887
0
        p->icc_params.max_luma = 0.0f;
888
0
    } else {
889
0
        pl_color_space_nominal_luma_ex(pl_nominal_luma_params(
890
0
            .color    = &target->color,
891
0
            .metadata = PL_HDR_METADATA_HDR10, // use only static HDR nits
892
0
            .scaling  = PL_HDR_NITS,
893
0
            .out_max  = &p->icc_params.max_luma,
894
0
        ));
895
0
    }
896
897
0
    pl_icc_update(p->pllog, &p->icc_profile, NULL, &p->icc_params);
898
0
    target->icc = p->icc_profile;
899
0
}
900
901
static void apply_crop(struct pl_frame *frame, struct mp_rect crop,
902
                       int width, int height)
903
0
{
904
0
    frame->crop = (struct pl_rect2df) {
905
0
        .x0 = crop.x0,
906
0
        .y0 = crop.y0,
907
0
        .x1 = crop.x1,
908
0
        .y1 = crop.y1,
909
0
    };
910
911
    // mpv gives us rotated/flipped rects, libplacebo expects unrotated
912
0
    pl_rect2df_rotate(&frame->crop, -frame->rotation);
913
0
    if (frame->crop.x1 < frame->crop.x0) {
914
0
        frame->crop.x0 = width - frame->crop.x0;
915
0
        frame->crop.x1 = width - frame->crop.x1;
916
0
    }
917
918
0
    if (frame->crop.y1 < frame->crop.y0) {
919
0
        frame->crop.y0 = height - frame->crop.y0;
920
0
        frame->crop.y1 = height - frame->crop.y1;
921
0
    }
922
0
}
923
924
static void update_tm_viz(struct pl_color_map_params *params,
925
                          const struct pl_frame *target)
926
0
{
927
0
    if (!params->visualize_lut)
928
0
        return;
929
930
    // Use right half of screen for TM visualization, constrain to 1:1 AR
931
0
    const float out_w = fabsf(pl_rect_w(target->crop));
932
0
    const float out_h = fabsf(pl_rect_h(target->crop));
933
0
    const float size = MPMIN(out_w / 2.0f, out_h);
934
0
    params->visualize_rect = (pl_rect2df) {
935
0
        .x0 = 1.0f - size / out_w,
936
0
        .x1 = 1.0f,
937
0
        .y0 = 0.0f,
938
0
        .y1 = size / out_h,
939
0
    };
940
941
    // Visualize red-blue plane
942
0
    params->visualize_hue = M_PI / 4.0;
943
0
}
944
945
static enum pl_color_primaries get_best_prim_container(const struct pl_raw_primaries *gamut)
946
0
{
947
0
    enum pl_color_primaries container = PL_COLOR_PRIM_UNKNOWN;
948
949
0
    if (!pl_primaries_valid(gamut))
950
0
        return container;
951
952
0
    const struct pl_raw_primaries *best = NULL;
953
0
    for (enum pl_color_primaries prim = 1; prim < PL_COLOR_PRIM_COUNT; prim++) {
954
0
        const struct pl_raw_primaries *raw = pl_raw_primaries_get(prim);
955
0
        if (pl_raw_primaries_similar(raw, gamut)) {
956
0
            container = prim;
957
0
            best = raw;
958
0
            break;
959
0
        }
960
961
0
        if (pl_primaries_superset(raw, gamut) &&
962
0
            (!best || pl_primaries_superset(best, raw)))
963
0
        {
964
0
            container = prim;
965
0
            best = raw;
966
0
        }
967
0
    }
968
969
0
    if (!best)
970
0
        container = PL_COLOR_PRIM_BT_2020;
971
972
0
    return container;
973
0
}
974
975
static void update_hook_opts_dynamic(struct priv *p, const struct pl_hook *hook,
976
                                     const struct mp_image *mpi);
977
978
static bool draw_frame(struct vo *vo, struct vo_frame *frame)
979
0
{
980
0
    struct priv *p = vo->priv;
981
0
    pl_options pars = p->pars;
982
0
    pl_gpu gpu = p->gpu;
983
0
    update_options(vo);
984
985
0
    struct pl_render_params params = pars->params;
986
0
    const struct gl_video_opts *opts = p->opts_cache->opts;
987
0
    bool will_redraw = frame->display_synced && frame->num_vsyncs > 1;
988
0
    bool cache_frame = will_redraw || frame->still;
989
0
    bool can_interpolate = opts->interpolation && frame->display_synced &&
990
0
                           !frame->still && frame->num_frames > 1;
991
0
    double pts_offset = can_interpolate ? frame->ideal_frame_vsync : 0;
992
0
    params.info_callback = info_callback;
993
0
    params.info_priv = vo;
994
0
    params.skip_caching_single_frame = !cache_frame;
995
0
    params.preserve_mixing_cache = p->next_opts->inter_preserve && !frame->still;
996
0
    if (frame->still)
997
0
        params.frame_mixer = NULL;
998
999
0
    if (frame->current && frame->current->params.vflip) {
1000
0
        pl_matrix2x2 m = { .m = {{1, 0}, {0, -1}}, };
1001
0
        pars->distort_params.transform.mat = m;
1002
0
        params.distort_params = &pars->distort_params;
1003
0
    } else {
1004
0
        params.distort_params = NULL;
1005
0
    }
1006
1007
    // pl_queue advances its internal virtual PTS and culls available frames
1008
    // based on this value and the VPS/FPS ratio. Requesting a non-monotonic PTS
1009
    // is an invalid use of pl_queue. Reset it if this happens in an attempt to
1010
    // recover as much as possible. Ideally, this should never occur, and if it
1011
    // does, it should be corrected. The ideal_frame_vsync may be negative if
1012
    // the last draw did not align perfectly with the vsync. In this case, we
1013
    // should have the previous frame available in pl_queue, or a reset is
1014
    // already requested. Clamp the check to 0, as we don't have the previous
1015
    // frame in vo_frame anyway.
1016
0
    struct pl_source_frame vpts;
1017
0
    if (frame->current && !p->want_reset) {
1018
0
        if (pl_queue_peek(p->queue, 0, &vpts) &&
1019
0
            frame->current->pts + MPMAX(0, pts_offset) < vpts.pts)
1020
0
        {
1021
0
            MP_VERBOSE(vo, "Forcing queue refill, PTS(%f + %f | %f) < VPTS(%f)\n",
1022
0
                       frame->current->pts, pts_offset,
1023
0
                       frame->ideal_frame_vsync_duration, vpts.pts);
1024
0
            p->want_reset = true;
1025
0
        }
1026
0
    }
1027
1028
    // Push all incoming frames into the frame queue
1029
0
    for (int n = 0; n < frame->num_frames; n++) {
1030
0
        int id = frame->frame_id + n;
1031
1032
0
        if (p->want_reset) {
1033
0
            pl_renderer_flush_cache(p->rr);
1034
0
            pl_queue_reset(p->queue);
1035
0
            p->last_pts = 0.0;
1036
0
            p->last_id = 0;
1037
0
            p->want_reset = false;
1038
0
        }
1039
1040
0
        if (id <= p->last_id)
1041
0
            continue; // ignore already seen frames
1042
1043
0
        struct mp_image *mpi = mp_image_new_ref(frame->frames[n]);
1044
0
        struct frame_priv *fp = talloc_zero(mpi, struct frame_priv);
1045
0
        mpi->priv = fp;
1046
0
        fp->vo = vo;
1047
1048
0
        pl_queue_push(p->queue, &(struct pl_source_frame) {
1049
0
            .pts = mpi->pts,
1050
0
            .duration = can_interpolate ? frame->approx_duration : 0,
1051
0
            .frame_data = mpi,
1052
0
            .map = map_frame,
1053
0
            .unmap = unmap_frame,
1054
0
            .discard = discard_frame,
1055
0
        });
1056
1057
0
        p->last_id = id;
1058
0
    }
1059
1060
0
    struct ra_swapchain *sw = p->ra_ctx->swapchain;
1061
1062
0
    bool pass_colorspace = false;
1063
0
    struct pl_color_space target_csp = {0};
1064
    // TODO: Implement this for all backends
1065
0
    if (sw->fns->target_csp)
1066
0
        target_csp = sw->fns->target_csp(sw);
1067
0
    if (target_csp.primaries == PL_COLOR_PRIM_UNKNOWN)
1068
0
        target_csp.primaries = get_best_prim_container(&target_csp.hdr.prim);
1069
0
    if (!pl_color_transfer_is_hdr(target_csp.transfer)) {
1070
        // Don't use reported display peak in SDR mode. Mostly because libplacebo
1071
        // forcefully switches to PQ if hinting hdr metadata, ignoring the transfer
1072
        // set in the hint. But also because setting target peak in SDR mode is
1073
        // very specific usecase, needs proper calibration, users can set it manually.
1074
0
        target_csp.hdr.max_luma = 0;
1075
0
        target_csp.hdr.min_luma = 0;
1076
0
        target_csp.hdr.max_cll = 0;
1077
0
        target_csp.hdr.max_fall = 0;
1078
0
    }
1079
    // maxFALL in display metadata is in fact MaxFullFrameLuminance. Wayland
1080
    // reports it as maxFALL directly, but this doesn't mean the same thing.
1081
0
    target_csp.hdr.max_fall = 0;
1082
1083
0
    struct pl_color_space hint;
1084
0
    bool target_hint = p->next_opts->target_hint == 1 ||
1085
0
                       (p->next_opts->target_hint == -1 &&
1086
0
                        target_csp.transfer != PL_COLOR_TRC_UNKNOWN);
1087
    // Assume HDR is supported, if target_csp() is not available
1088
0
    if (target_csp.transfer == PL_COLOR_TRC_UNKNOWN) {
1089
0
        target_csp = (struct pl_color_space){
1090
0
            .transfer = opts->target_trc ? opts->target_trc : pl_color_space_hdr10.transfer };
1091
0
    }
1092
0
    if (target_hint && frame->current) {
1093
0
        const struct pl_color_space *source = &frame->current->params.color;
1094
0
        const struct pl_color_space *target = &target_csp;
1095
0
        hint = *source;
1096
0
        if (p->next_opts->target_hint_mode == 0) {
1097
0
            hint = *target;
1098
0
            if (pl_color_transfer_is_hdr(hint.transfer) && !pl_primaries_valid(&hint.hdr.prim))
1099
0
                pl_color_space_merge(&hint, source);
1100
            // Restore target luminance if it was present, note that we check
1101
            // max_luma only, this make sure that max_cll/max_fall is not take
1102
            // from source.
1103
0
            if (target->hdr.max_luma) {
1104
0
                hint.hdr.max_luma = target->hdr.max_luma;
1105
0
                hint.hdr.min_luma = target->hdr.min_luma;
1106
0
                hint.hdr.max_cll  = target->hdr.max_cll;
1107
0
                hint.hdr.max_fall = target->hdr.max_fall;
1108
0
            }
1109
0
        }
1110
0
        if (p->next_opts->target_hint_mode == 2) { // source-dynamic
1111
0
            pl_color_space_nominal_luma_ex(pl_nominal_luma_params(
1112
0
                .color      = &hint,
1113
0
                .metadata   = PL_HDR_METADATA_ANY,
1114
0
                .scaling    = PL_HDR_NITS,
1115
0
                .out_min    = &hint.hdr.min_luma,
1116
0
                .out_max    = &hint.hdr.max_luma,
1117
0
            ));
1118
            // Set maxCLL to dynamic max luminance. Note that libplacebo uses
1119
            // max luminace as maxCLL in practice.
1120
0
            hint.hdr.max_cll = hint.hdr.max_luma;
1121
            // Keep maxFALL from static metadata, unless its value is too high.
1122
            // Could be set to 0, but let's keep it for now.
1123
0
            if (hint.hdr.max_fall > hint.hdr.max_cll)
1124
0
                hint.hdr.max_fall = 0;
1125
0
        }
1126
        // Infer missing bits now. This is important so that we don't lose
1127
        // information after user option overrides. For example, if the user
1128
        // sets target_trc to PQ, but the hint(source) is SDR, we want to fill
1129
        // in SDR luminance values instead of the default PQ range.
1130
0
        struct pl_color_space source_csp = *source;
1131
0
        pl_color_space_infer_map(&source_csp, &hint);
1132
        // Always prefer target luminance and transfer for inverse tone mapping
1133
0
        if (pl_color_transfer_is_hdr(target->transfer) && opts->tone_map.inverse) {
1134
0
            hint.transfer     = target->transfer;
1135
0
            hint.hdr.max_luma = target->hdr.max_luma;
1136
0
            hint.hdr.min_luma = target->hdr.min_luma;
1137
0
            hint.hdr.max_cll  = target->hdr.max_cll;
1138
0
            hint.hdr.max_fall = target->hdr.max_fall;
1139
0
        }
1140
0
        if (p->ra_ctx->fns->pass_colorspace && p->ra_ctx->fns->pass_colorspace(p->ra_ctx))
1141
0
            pass_colorspace = true;
1142
0
        if (opts->target_prim)
1143
0
            hint.primaries = opts->target_prim;
1144
0
        if (opts->target_trc)
1145
0
            hint.transfer = opts->target_trc;
1146
0
        if (opts->target_peak)
1147
0
            hint.hdr.max_luma = opts->target_peak;
1148
        // Always set maxCLL, display uses this metadata and we shouldn't let it
1149
        // fallback to default value.
1150
0
        if (!hint.hdr.max_cll)
1151
0
            hint.hdr.max_cll = hint.hdr.max_luma;
1152
        // If tone mapping is required, adjust maxCLL and maxFALL
1153
0
        if (source->hdr.max_luma > hint.hdr.max_luma || opts->tone_map.inverse) {
1154
            // Set maxCLL to the target luminance if it's not already lower
1155
0
            if (!hint.hdr.max_cll || hint.hdr.max_luma < hint.hdr.max_cll || opts->tone_map.inverse)
1156
0
                hint.hdr.max_cll = hint.hdr.max_luma;
1157
            // There's no reliable way to estimate maxFALL here
1158
0
            hint.hdr.max_fall = 0;
1159
0
        }
1160
0
        if (hint.hdr.max_cll && hint.hdr.max_fall > hint.hdr.max_cll)
1161
0
            hint.hdr.max_fall = 0;
1162
0
        apply_target_contrast(p, &hint, hint.hdr.min_luma);
1163
0
        if (!pass_colorspace)
1164
0
            pl_swapchain_colorspace_hint(p->sw, &hint);
1165
0
    } else if (!target_hint) {
1166
0
        pl_swapchain_colorspace_hint(p->sw, NULL);
1167
0
    }
1168
1169
0
    struct pl_swapchain_frame swframe;
1170
0
    bool should_draw = sw->fns->start_frame(sw, NULL); // for wayland logic
1171
0
    if (!should_draw || !pl_swapchain_start_frame(p->sw, &swframe)) {
1172
0
        if (frame->current) {
1173
            // Advance the queue state to the current PTS to discard unused frames
1174
0
            struct pl_queue_params qparams = *pl_queue_params(
1175
0
                .pts = frame->current->pts + pts_offset,
1176
0
                .radius = pl_frame_mix_radius(&params),
1177
0
                .vsync_duration = can_interpolate ? frame->ideal_frame_vsync_duration : 0,
1178
0
            );
1179
0
#if PL_API_VER >= 340
1180
0
            qparams.drift_compensation = 0;
1181
0
#endif
1182
0
            pl_queue_update(p->queue, NULL, &qparams);
1183
0
        }
1184
0
        return VO_FALSE;
1185
0
    }
1186
1187
0
    bool valid = false;
1188
0
    p->is_interpolated = false;
1189
1190
    // Calculate target
1191
0
    struct pl_frame target;
1192
0
    pl_frame_from_swapchain(&target, &swframe);
1193
0
    apply_target_options(p, &target, hint.hdr.min_luma, target_hint && !pass_colorspace);
1194
0
    update_overlays(vo, p->osd_res,
1195
0
                    (frame->current && opts->blend_subs) ? OSD_DRAW_OSD_ONLY : 0,
1196
0
                    PL_OVERLAY_COORDS_DST_FRAME, &p->osd_state, &target, frame->current);
1197
0
    apply_crop(&target, p->dst, swframe.fbo->params.w, swframe.fbo->params.h);
1198
0
    update_tm_viz(&pars->color_map_params, &target);
1199
1200
0
    struct pl_frame_mix mix = {0};
1201
0
    if (frame->current) {
1202
        // Update queue state
1203
0
        struct pl_queue_params qparams = *pl_queue_params(
1204
0
            .pts = frame->current->pts + pts_offset,
1205
0
            .radius = pl_frame_mix_radius(&params),
1206
0
            .vsync_duration = can_interpolate ? frame->ideal_frame_vsync_duration : 0,
1207
0
            .interpolation_threshold = opts->interpolation_threshold,
1208
0
        );
1209
0
#if PL_API_VER >= 340
1210
0
        qparams.drift_compensation = 0;
1211
0
#endif
1212
1213
        // Depending on the vsync ratio, we may be up to half of the vsync
1214
        // duration before the current frame time. This works fine because
1215
        // pl_queue will have this frame, unless it's after a reset event. In
1216
        // this case, start from the first available frame.
1217
0
        struct pl_source_frame first;
1218
0
        if (pl_queue_peek(p->queue, 0, &first) && qparams.pts < first.pts) {
1219
0
            if (first.pts != frame->current->pts)
1220
0
                MP_VERBOSE(vo, "Current PTS(%f) != VPTS(%f)\n", frame->current->pts, first.pts);
1221
0
            MP_VERBOSE(vo, "Clamping first frame PTS from %f to %f\n", qparams.pts, first.pts);
1222
0
            qparams.pts = first.pts;
1223
0
        }
1224
0
        p->last_pts = qparams.pts;
1225
1226
0
        switch (pl_queue_update(p->queue, &mix, &qparams)) {
1227
0
        case PL_QUEUE_ERR:
1228
0
            MP_ERR(vo, "Failed updating frames!\n");
1229
0
            goto done;
1230
0
        case PL_QUEUE_EOF:
1231
0
            abort(); // we never signal EOF
1232
0
        case PL_QUEUE_MORE:
1233
            // This is expected to happen semi-frequently near the start and
1234
            // end of a file, so only log it at high verbosity and move on.
1235
0
            MP_DBG(vo, "Render queue underrun.\n");
1236
0
            break;
1237
0
        case PL_QUEUE_OK:
1238
0
            break;
1239
0
        }
1240
1241
        // Update source crop and overlays on all existing frames. We
1242
        // technically own the `pl_frame` struct so this is kosher. This could
1243
        // be partially avoided by instead flushing the queue on resizes, but
1244
        // doing it this way avoids unnecessarily re-uploading frames.
1245
0
        for (int i = 0; i < mix.num_frames; i++) {
1246
0
            struct pl_frame *image = (struct pl_frame *) mix.frames[i];
1247
0
            struct mp_image *mpi = image->user_data;
1248
0
            struct frame_priv *fp = mpi->priv;
1249
0
            apply_crop(image, p->src, vo->params->w, vo->params->h);
1250
0
            if (opts->blend_subs) {
1251
0
                if (frame->redraw)
1252
0
                    p->osd_sync++;
1253
0
                if (fp->osd_sync < p->osd_sync) {
1254
0
                    float w = pl_rect_w(opts->blend_subs == BLEND_SUBS_VIDEO ? image->crop : target.crop);
1255
0
                    float h = pl_rect_h(opts->blend_subs == BLEND_SUBS_VIDEO ? image->crop : target.crop);
1256
0
                    float rx = w / pl_rect_w(image->crop);
1257
0
                    float ry = h / pl_rect_h(image->crop);
1258
0
                    struct mp_osd_res res = {
1259
0
                        .w = w,
1260
0
                        .h = h,
1261
0
                        .ml = -image->crop.x0 * rx,
1262
0
                        .mr = (image->crop.x1 - vo->params->w) * rx,
1263
0
                        .mt = -image->crop.y0 * ry,
1264
0
                        .mb = (image->crop.y1 - vo->params->h) * ry,
1265
0
                        .display_par = 1.0,
1266
0
                    };
1267
0
                    enum pl_overlay_coords rel = opts->blend_subs == BLEND_SUBS_VIDEO
1268
0
                        ? PL_OVERLAY_COORDS_SRC_CROP : PL_OVERLAY_COORDS_DST_CROP;
1269
0
                    update_overlays(vo, res, OSD_DRAW_SUB_ONLY,
1270
0
                                    rel, &fp->subs, image, mpi);
1271
0
                    fp->osd_sync = p->osd_sync;
1272
0
                }
1273
0
            } else {
1274
                // Disable overlays when blend_subs is disabled
1275
0
                image->num_overlays = 0;
1276
0
                fp->osd_sync = 0;
1277
0
            }
1278
1279
            // Update the frame signature to include the current OSD sync
1280
            // value, in order to disambiguate between identical frames with
1281
            // modified OSD. Shift the OSD sync value by a lot to avoid
1282
            // collisions with low signature values.
1283
            //
1284
            // This is safe to do because `pl_frame_mix.signature` lives in
1285
            // temporary memory that is only valid for this `pl_queue_update`.
1286
0
            ((uint64_t *) mix.signatures)[i] ^= fp->osd_sync << 48;
1287
0
        }
1288
1289
        // Update dynamic hook parameters
1290
0
        for (int i = 0; i < pars->params.num_hooks; i++)
1291
0
            update_hook_opts_dynamic(p, p->hooks[i], frame->current);
1292
0
    }
1293
1294
    // Render frame
1295
0
    if (!pl_render_image_mix(p->rr, &mix, &target, &params)) {
1296
0
        MP_ERR(vo, "Failed rendering frame!\n");
1297
0
        goto done;
1298
0
    }
1299
1300
0
    struct pl_frame ref_frame;
1301
0
    pl_frames_infer_mix(p->rr, &mix, &target, &ref_frame);
1302
1303
0
    mp_mutex_lock(&vo->params_mutex);
1304
0
    p->target_params = (struct mp_image_params){
1305
0
        .imgfmt_name = swframe.fbo->params.format
1306
0
                        ? swframe.fbo->params.format->name : NULL,
1307
0
        .w = mp_rect_w(p->dst),
1308
0
        .h = mp_rect_h(p->dst),
1309
0
        .color = pass_colorspace ? hint : target.color,
1310
0
        .repr = target.repr,
1311
0
        .rotate = target.rotation,
1312
0
    };
1313
0
    vo->target_params = &p->target_params;
1314
1315
0
    if (vo->params) {
1316
        // Augment metadata with peak detection max_pq_y / avg_pq_y
1317
0
        vo->has_peak_detect_values = pl_renderer_get_hdr_metadata(p->rr, &vo->params->color.hdr);
1318
0
    }
1319
0
    mp_mutex_unlock(&vo->params_mutex);
1320
1321
0
    p->is_interpolated = pts_offset != 0 && mix.num_frames > 1;
1322
0
    valid = true;
1323
    // fall through
1324
1325
0
done:
1326
0
    if (!valid) // clear with purple to indicate error
1327
0
        pl_tex_clear(gpu, swframe.fbo, (float[4]){ 0.5, 0.0, 1.0, 1.0 });
1328
1329
0
    pl_gpu_flush(gpu);
1330
0
    p->frame_pending = true;
1331
0
    return VO_TRUE;
1332
0
}
1333
1334
static void flip_page(struct vo *vo)
1335
0
{
1336
0
    struct priv *p = vo->priv;
1337
0
    struct ra_swapchain *sw = p->ra_ctx->swapchain;
1338
1339
0
    if (p->frame_pending) {
1340
0
        if (!pl_swapchain_submit_frame(p->sw))
1341
0
            MP_ERR(vo, "Failed presenting frame!\n");
1342
0
        p->frame_pending = false;
1343
0
    }
1344
1345
0
    sw->fns->swap_buffers(sw);
1346
0
}
1347
1348
static void get_vsync(struct vo *vo, struct vo_vsync_info *info)
1349
0
{
1350
0
    struct priv *p = vo->priv;
1351
0
    struct ra_swapchain *sw = p->ra_ctx->swapchain;
1352
0
    if (sw->fns->get_vsync)
1353
0
        sw->fns->get_vsync(sw, info);
1354
0
}
1355
1356
static int query_format(struct vo *vo, int format)
1357
0
{
1358
0
    struct priv *p = vo->priv;
1359
0
    if (ra_hwdec_get(&p->hwdec_ctx, format))
1360
0
        return true;
1361
1362
0
    bool supported = format_supported(vo, format, false);
1363
0
    if (!supported)
1364
0
        supported = format_supported(vo, format, true);
1365
1366
0
    return supported;
1367
0
}
1368
1369
static void resize(struct vo *vo)
1370
0
{
1371
0
    struct priv *p = vo->priv;
1372
0
    struct mp_rect src, dst;
1373
0
    struct mp_osd_res osd;
1374
0
    vo_get_src_dst_rects(vo, &src, &dst, &osd);
1375
0
    if (vo->dwidth && vo->dheight) {
1376
0
        gpu_ctx_resize(p->context, vo->dwidth, vo->dheight);
1377
0
        vo->want_redraw = true;
1378
0
    }
1379
1380
0
    if (mp_rect_equals(&p->src, &src) &&
1381
0
        mp_rect_equals(&p->dst, &dst) &&
1382
0
        osd_res_equals(p->osd_res, osd))
1383
0
        return;
1384
1385
0
    p->osd_sync++;
1386
0
    p->osd_res = osd;
1387
0
    p->src = src;
1388
0
    p->dst = dst;
1389
0
}
1390
1391
static int reconfig(struct vo *vo, struct mp_image_params *params)
1392
0
{
1393
0
    struct priv *p = vo->priv;
1394
0
    if (!p->ra_ctx->fns->reconfig(p->ra_ctx))
1395
0
        return -1;
1396
1397
0
    resize(vo);
1398
0
    mp_mutex_lock(&vo->params_mutex);
1399
0
    vo->target_params = NULL;
1400
0
    mp_mutex_unlock(&vo->params_mutex);
1401
0
    return 0;
1402
0
}
1403
1404
// Takes over ownership of `icc`. Can be used to unload profile (icc.len == 0)
1405
static bool update_icc(struct priv *p, struct bstr icc)
1406
0
{
1407
0
    struct pl_icc_profile profile = {
1408
0
        .data = icc.start,
1409
0
        .len  = icc.len,
1410
0
    };
1411
1412
0
    pl_icc_profile_compute_signature(&profile);
1413
1414
0
    bool ok = pl_icc_update(p->pllog, &p->icc_profile, &profile, &p->icc_params);
1415
0
    talloc_free(icc.start);
1416
0
    return ok;
1417
0
}
1418
1419
// Returns whether the ICC profile was updated (even on failure)
1420
static bool update_auto_profile(struct priv *p, int *events)
1421
0
{
1422
0
    const struct gl_video_opts *opts = p->opts_cache->opts;
1423
0
    if (!opts->icc_opts || !opts->icc_opts->profile_auto || p->icc_path)
1424
0
        return false;
1425
1426
0
    MP_VERBOSE(p, "Querying ICC profile...\n");
1427
0
    bstr icc = {0};
1428
0
    int r = p->ra_ctx->fns->control(p->ra_ctx, events, VOCTRL_GET_ICC_PROFILE, &icc);
1429
1430
0
    if (r != VO_NOTAVAIL) {
1431
0
        if (r == VO_FALSE) {
1432
0
            MP_WARN(p, "Could not retrieve an ICC profile.\n");
1433
0
        } else if (r == VO_NOTIMPL) {
1434
0
            MP_ERR(p, "icc-profile-auto not implemented on this platform.\n");
1435
0
        }
1436
1437
0
        update_icc(p, icc);
1438
0
        return true;
1439
0
    }
1440
1441
0
    return false;
1442
0
}
1443
1444
static void video_screenshot(struct vo *vo, struct voctrl_screenshot *args)
1445
0
{
1446
0
    struct priv *p = vo->priv;
1447
0
    pl_options pars = p->pars;
1448
0
    pl_gpu gpu = p->gpu;
1449
0
    pl_tex fbo = NULL;
1450
0
    args->res = NULL;
1451
1452
0
    update_options(vo);
1453
0
    struct pl_render_params params = pars->params;
1454
0
    params.info_callback = NULL;
1455
0
    params.skip_caching_single_frame = true;
1456
0
    params.preserve_mixing_cache = false;
1457
0
    params.frame_mixer = NULL;
1458
1459
0
    struct pl_peak_detect_params peak_params;
1460
0
    if (params.peak_detect_params) {
1461
0
        peak_params = *params.peak_detect_params;
1462
0
        params.peak_detect_params = &peak_params;
1463
0
        peak_params.allow_delayed = false;
1464
0
    }
1465
1466
    // Retrieve the current frame from the frame queue
1467
0
    struct pl_frame_mix mix;
1468
0
    enum pl_queue_status status;
1469
0
    struct pl_queue_params qparams = *pl_queue_params(
1470
0
        .pts = p->last_pts,
1471
0
    );
1472
0
#if PL_API_VER >= 340
1473
0
        qparams.drift_compensation = 0;
1474
0
#endif
1475
0
    status = pl_queue_update(p->queue, &mix, &qparams);
1476
0
    mp_assert(status != PL_QUEUE_EOF);
1477
0
    if (status == PL_QUEUE_ERR) {
1478
0
        MP_ERR(vo, "Unknown error occurred while trying to take screenshot!\n");
1479
0
        return;
1480
0
    }
1481
0
    if (!mix.num_frames) {
1482
0
        MP_ERR(vo, "No frames available to take screenshot of, is a file loaded?\n");
1483
0
        return;
1484
0
    }
1485
1486
    // Passing an interpolation radius of 0 guarantees that the first frame in
1487
    // the resulting mix is the correct frame for this PTS
1488
0
    struct pl_frame image = *(struct pl_frame *) mix.frames[0];
1489
0
    struct mp_image *mpi = image.user_data;
1490
0
    struct mp_rect src = p->src, dst = p->dst;
1491
0
    struct mp_osd_res osd = p->osd_res;
1492
0
    if (!args->scaled) {
1493
0
        int w, h;
1494
0
        mp_image_params_get_dsize(&mpi->params, &w, &h);
1495
0
        if (w < 1 || h < 1)
1496
0
            return;
1497
1498
0
        int src_w = mpi->params.w;
1499
0
        int src_h = mpi->params.h;
1500
0
        src = (struct mp_rect) {0, 0, src_w, src_h};
1501
0
        dst = (struct mp_rect) {0, 0, w, h};
1502
1503
0
        if (mp_image_crop_valid(&mpi->params))
1504
0
            src = mpi->params.crop;
1505
1506
0
        if (mpi->params.rotate % 180 == 90) {
1507
0
            MPSWAP(int, w, h);
1508
0
            MPSWAP(int, src_w, src_h);
1509
0
        }
1510
0
        mp_rect_rotate(&src, src_w, src_h, mpi->params.rotate);
1511
0
        mp_rect_rotate(&dst, w, h, mpi->params.rotate);
1512
1513
0
        osd = (struct mp_osd_res) {
1514
0
            .display_par = 1.0,
1515
0
            .w = mp_rect_w(dst),
1516
0
            .h = mp_rect_h(dst),
1517
0
        };
1518
0
    }
1519
1520
    // Create target FBO, try high bit depth first
1521
0
    int mpfmt;
1522
0
    for (int depth = args->high_bit_depth ? 16 : 8; depth; depth -= 8) {
1523
0
        if (depth == 16) {
1524
0
            mpfmt = IMGFMT_RGBA64;
1525
0
        } else {
1526
0
            mpfmt = p->ra_ctx->opts.want_alpha ? IMGFMT_RGBA : IMGFMT_RGB0;
1527
0
        }
1528
0
        pl_fmt fmt = pl_find_fmt(gpu, PL_FMT_UNORM, 4, depth, depth,
1529
0
                                 PL_FMT_CAP_RENDERABLE | PL_FMT_CAP_HOST_READABLE);
1530
0
        if (!fmt)
1531
0
            continue;
1532
1533
0
        fbo = pl_tex_create(gpu, pl_tex_params(
1534
0
            .w = osd.w,
1535
0
            .h = osd.h,
1536
0
            .format = fmt,
1537
0
            .blit_dst = true,
1538
0
            .renderable = true,
1539
0
            .host_readable = true,
1540
0
            .storable = fmt->caps & PL_FMT_CAP_STORABLE,
1541
0
        ));
1542
0
        if (fbo)
1543
0
            break;
1544
0
    }
1545
1546
0
    if (!fbo) {
1547
0
        MP_ERR(vo, "Failed creating target FBO for screenshot!\n");
1548
0
        return;
1549
0
    }
1550
1551
0
    struct pl_frame target = {
1552
0
        .repr = pl_color_repr_rgb,
1553
0
        .num_planes = 1,
1554
0
        .planes[0] = {
1555
0
            .texture = fbo,
1556
0
            .components = 4,
1557
0
            .component_mapping = {0, 1, 2, 3},
1558
0
        },
1559
0
    };
1560
1561
0
    const struct gl_video_opts *opts = p->opts_cache->opts;
1562
0
    if (args->scaled) {
1563
        // Apply target LUT, ICC profile and CSP override only in window mode
1564
0
        apply_target_options(p, &target, 0, false);
1565
0
    } else if (args->native_csp) {
1566
0
        target.color = image.color;
1567
0
    } else {
1568
0
        target.color = pl_color_space_srgb;
1569
0
    }
1570
1571
0
    apply_crop(&image, src, mpi->params.w, mpi->params.h);
1572
0
    apply_crop(&target, dst, fbo->params.w, fbo->params.h);
1573
0
    update_tm_viz(&pars->color_map_params, &target);
1574
1575
0
    int osd_flags = 0;
1576
0
    if (!args->subs)
1577
0
        osd_flags |= OSD_DRAW_OSD_ONLY;
1578
0
    if (!args->osd)
1579
0
        osd_flags |= OSD_DRAW_SUB_ONLY;
1580
1581
0
    struct frame_priv *fp = mpi->priv;
1582
0
    if (opts->blend_subs) {
1583
0
        float w = pl_rect_w(opts->blend_subs == BLEND_SUBS_VIDEO ? image.crop : target.crop);
1584
0
        float h = pl_rect_h(opts->blend_subs == BLEND_SUBS_VIDEO ? image.crop : target.crop);
1585
0
        float rx = w / pl_rect_w(image.crop);
1586
0
        float ry = h / pl_rect_h(image.crop);
1587
0
        struct mp_osd_res res = {
1588
0
            .w = w,
1589
0
            .h = h,
1590
0
            .ml = -image.crop.x0 * rx,
1591
0
            .mr = (image.crop.x1 - vo->params->w) * rx,
1592
0
            .mt = -image.crop.y0 * ry,
1593
0
            .mb = (image.crop.y1 - vo->params->h) * ry,
1594
0
            .display_par = 1.0,
1595
0
        };
1596
0
        enum pl_overlay_coords rel = opts->blend_subs == BLEND_SUBS_VIDEO
1597
0
            ? PL_OVERLAY_COORDS_SRC_CROP : PL_OVERLAY_COORDS_DST_CROP;
1598
0
        update_overlays(vo, res, osd_flags,
1599
0
                        rel, &fp->subs, &image, mpi);
1600
0
    } else {
1601
        // Disable overlays when blend_subs is disabled
1602
0
        update_overlays(vo, osd, osd_flags, PL_OVERLAY_COORDS_DST_FRAME,
1603
0
                        &p->osd_state, &target, mpi);
1604
0
        image.num_overlays = 0;
1605
0
    }
1606
1607
0
    if (!pl_render_image(p->rr, &image, &target, &params)) {
1608
0
        MP_ERR(vo, "Failed rendering frame!\n");
1609
0
        goto done;
1610
0
    }
1611
1612
0
    args->res = mp_image_alloc(mpfmt, fbo->params.w, fbo->params.h);
1613
0
    if (!args->res)
1614
0
        goto done;
1615
1616
0
    args->res->params.color.primaries = target.color.primaries;
1617
0
    args->res->params.color.transfer = target.color.transfer;
1618
0
    args->res->params.repr.levels = target.repr.levels;
1619
0
    args->res->params.color.hdr = target.color.hdr;
1620
0
    if (args->scaled)
1621
0
        args->res->params.p_w = args->res->params.p_h = 1;
1622
1623
0
    bool ok = pl_tex_download(gpu, pl_tex_transfer_params(
1624
0
        .tex = fbo,
1625
0
        .ptr = args->res->planes[0],
1626
0
        .row_pitch = args->res->stride[0],
1627
0
    ));
1628
1629
0
    if (!ok)
1630
0
        TA_FREEP(&args->res);
1631
1632
    // fall through
1633
0
done:
1634
0
    pl_tex_destroy(gpu, &fbo);
1635
0
}
1636
1637
static inline void copy_frame_info_to_mp(struct frame_info *pl,
1638
0
                                         struct mp_frame_perf *mp) {
1639
0
    static_assert(MP_ARRAY_SIZE(pl->info) == MP_ARRAY_SIZE(mp->perf), "");
1640
0
    mp_assert(pl->count <= VO_PASS_PERF_MAX);
1641
0
    mp->count = MPMIN(pl->count, VO_PASS_PERF_MAX);
1642
1643
0
    for (int i = 0; i < mp->count; ++i) {
1644
0
        const struct pl_dispatch_info *pass = &pl->info[i];
1645
1646
0
        static_assert(VO_PERF_SAMPLE_COUNT >= MP_ARRAY_SIZE(pass->samples), "");
1647
0
        mp_assert(pass->num_samples <= MP_ARRAY_SIZE(pass->samples));
1648
1649
0
        struct mp_pass_perf *perf = &mp->perf[i];
1650
0
        perf->count = MPMIN(pass->num_samples, VO_PERF_SAMPLE_COUNT);
1651
0
        memcpy(perf->samples, pass->samples, perf->count * sizeof(pass->samples[0]));
1652
0
        perf->last = pass->last;
1653
0
        perf->peak = pass->peak;
1654
0
        perf->avg = pass->average;
1655
1656
0
        strncpy(mp->desc[i], pass->shader->description, sizeof(mp->desc[i]) - 1);
1657
0
        mp->desc[i][sizeof(mp->desc[i]) - 1] = '\0';
1658
0
    }
1659
0
}
1660
1661
static void update_ra_ctx_options(struct vo *vo, struct ra_ctx_opts *ctx_opts)
1662
199
{
1663
199
    struct priv *p = vo->priv;
1664
199
    struct gl_video_opts *gl_opts = p->opts_cache->opts;
1665
199
    bool border_alpha = (p->next_opts->border_background == BACKGROUND_COLOR &&
1666
199
                         gl_opts->background_color.a != 255) ||
1667
199
                         p->next_opts->border_background == BACKGROUND_NONE;
1668
199
    ctx_opts->want_alpha = (gl_opts->background == BACKGROUND_COLOR &&
1669
199
                            gl_opts->background_color.a != 255) ||
1670
199
                            gl_opts->background == BACKGROUND_NONE ||
1671
199
                            border_alpha;
1672
199
}
1673
1674
static int control(struct vo *vo, uint32_t request, void *data)
1675
0
{
1676
0
    struct priv *p = vo->priv;
1677
1678
0
    switch (request) {
1679
0
    case VOCTRL_SET_PANSCAN:
1680
0
        resize(vo);
1681
0
        return VO_TRUE;
1682
0
    case VOCTRL_PAUSE:
1683
0
        if (p->is_interpolated)
1684
0
            vo->want_redraw = true;
1685
0
        return VO_TRUE;
1686
1687
0
    case VOCTRL_UPDATE_RENDER_OPTS: {
1688
0
        update_ra_ctx_options(vo, &p->ra_ctx->opts);
1689
0
        if (p->ra_ctx->fns->update_render_opts)
1690
0
            p->ra_ctx->fns->update_render_opts(p->ra_ctx);
1691
0
        vo->want_redraw = true;
1692
1693
        // Special case for --image-lut which requires a full reset.
1694
0
        int old_type = p->next_opts->image_lut.type;
1695
0
        update_options(vo);
1696
0
        struct user_lut image_lut = p->next_opts->image_lut;
1697
0
        p->want_reset |= image_lut.opt && ((!image_lut.path && image_lut.opt) ||
1698
0
                         (image_lut.path && strcmp(image_lut.path, image_lut.opt)) ||
1699
0
                         (old_type != image_lut.type));
1700
1701
        // Also re-query the auto profile, in case `update_render_options`
1702
        // unloaded a manually specified icc profile in favor of
1703
        // icc-profile-auto
1704
0
        int events = 0;
1705
0
        update_auto_profile(p, &events);
1706
0
        vo_event(vo, events);
1707
0
        return VO_TRUE;
1708
0
    }
1709
1710
0
    case VOCTRL_RESET:
1711
        // Defer until the first new frame (unique ID) actually arrives
1712
0
        p->want_reset = true;
1713
0
        return VO_TRUE;
1714
1715
0
    case VOCTRL_PERFORMANCE_DATA: {
1716
0
        struct voctrl_performance_data *perf = data;
1717
0
        copy_frame_info_to_mp(&p->perf_fresh, &perf->fresh);
1718
0
        copy_frame_info_to_mp(&p->perf_redraw, &perf->redraw);
1719
0
        return true;
1720
0
    }
1721
1722
0
    case VOCTRL_SCREENSHOT:
1723
0
        video_screenshot(vo, data);
1724
0
        return true;
1725
1726
0
    case VOCTRL_EXTERNAL_RESIZE:
1727
0
        reconfig(vo, NULL);
1728
0
        return true;
1729
1730
0
    case VOCTRL_LOAD_HWDEC_API:
1731
0
        ra_hwdec_ctx_load_fmt(&p->hwdec_ctx, vo->hwdec_devs, data);
1732
0
        return true;
1733
0
    }
1734
1735
0
    int events = 0;
1736
0
    int r = p->ra_ctx->fns->control(p->ra_ctx, &events, request, data);
1737
0
    if (events & VO_EVENT_ICC_PROFILE_CHANGED) {
1738
0
        if (update_auto_profile(p, &events))
1739
0
            vo->want_redraw = true;
1740
0
    }
1741
0
    if (events & VO_EVENT_RESIZE)
1742
0
        resize(vo);
1743
0
    if (events & VO_EVENT_EXPOSE)
1744
0
        vo->want_redraw = true;
1745
0
    vo_event(vo, events);
1746
1747
0
    return r;
1748
0
}
1749
1750
static void wakeup(struct vo *vo)
1751
0
{
1752
0
    struct priv *p = vo->priv;
1753
0
    if (p->ra_ctx && p->ra_ctx->fns->wakeup)
1754
0
        p->ra_ctx->fns->wakeup(p->ra_ctx);
1755
0
}
1756
1757
static void wait_events(struct vo *vo, int64_t until_time_ns)
1758
0
{
1759
0
    struct priv *p = vo->priv;
1760
0
    if (p->ra_ctx && p->ra_ctx->fns->wait_events) {
1761
0
        p->ra_ctx->fns->wait_events(p->ra_ctx, until_time_ns);
1762
0
    } else {
1763
0
        vo_wait_default(vo, until_time_ns);
1764
0
    }
1765
0
}
1766
1767
static char *cache_filepath(void *ta_ctx, char *dir, const char *prefix, uint64_t key)
1768
0
{
1769
0
    bstr filename = {0};
1770
0
    bstr_xappend_asprintf(ta_ctx, &filename, "%s_%016" PRIx64, prefix, key);
1771
0
    return mp_path_join_bstr(ta_ctx, bstr0(dir), filename);
1772
0
}
1773
1774
static pl_cache_obj cache_load_obj(void *p, uint64_t key)
1775
0
{
1776
0
    struct cache *c = p;
1777
0
    void *ta_ctx = talloc_new(NULL);
1778
0
    pl_cache_obj obj = {0};
1779
1780
0
    if (!c->dir)
1781
0
        goto done;
1782
1783
0
    char *filepath = cache_filepath(ta_ctx, c->dir, c->name, key);
1784
0
    if (!filepath)
1785
0
        goto done;
1786
1787
0
    if (stat(filepath, &(struct stat){0}))
1788
0
        goto done;
1789
1790
0
    int64_t load_start = mp_time_ns();
1791
0
    struct bstr data = stream_read_file(filepath, ta_ctx, c->global, STREAM_MAX_READ_SIZE);
1792
0
    int64_t load_end = mp_time_ns();
1793
0
    MP_DBG(c, "%s: key(%" PRIx64 "), size(%zu), load time(%.3f ms)\n",
1794
0
           __func__, key, data.len,
1795
0
           MP_TIME_NS_TO_MS(load_end - load_start));
1796
1797
0
    obj = (pl_cache_obj){
1798
0
        .key = key,
1799
0
        .data = talloc_steal(NULL, data.start),
1800
0
        .size = data.len,
1801
0
        .free = talloc_free,
1802
0
    };
1803
1804
0
done:
1805
0
    talloc_free(ta_ctx);
1806
0
    return obj;
1807
0
}
1808
1809
static void cache_save_obj(void *p, pl_cache_obj obj)
1810
0
{
1811
0
    const struct cache *c = p;
1812
0
    void *ta_ctx = talloc_new(NULL);
1813
1814
0
    if (!c->dir)
1815
0
        goto done;
1816
1817
0
    char *filepath = cache_filepath(ta_ctx, c->dir, c->name, obj.key);
1818
0
    if (!filepath)
1819
0
        goto done;
1820
1821
0
    if (!obj.data || !obj.size) {
1822
0
        unlink(filepath);
1823
0
        goto done;
1824
0
    }
1825
1826
    // Don't save if already exists
1827
0
    struct stat st;
1828
0
    if (!stat(filepath, &st) && st.st_size == obj.size) {
1829
0
        MP_DBG(c, "%s: key(%"PRIx64"), size(%zu)\n", __func__, obj.key, obj.size);
1830
0
        goto done;
1831
0
    }
1832
1833
0
    int64_t save_start = mp_time_ns();
1834
0
    mp_save_to_file(filepath, obj.data, obj.size);
1835
0
    int64_t save_end = mp_time_ns();
1836
0
    MP_DBG(c, "%s: key(%" PRIx64 "), size(%zu), save time(%.3f ms)\n",
1837
0
           __func__, obj.key, obj.size,
1838
0
           MP_TIME_NS_TO_MS(save_end - save_start));
1839
1840
0
done:
1841
0
    talloc_free(ta_ctx);
1842
0
}
1843
1844
static void cache_init(struct vo *vo, struct cache *cache, size_t max_size,
1845
                       const char *dir_opt)
1846
0
{
1847
0
    struct priv *p = vo->priv;
1848
0
    const char *name = cache == &p->shader_cache ? "shader" : "icc";
1849
0
    const size_t limit = cache == &p->shader_cache ? 128 << 20 : 1536 << 20;
1850
1851
0
    char *dir;
1852
0
    if (dir_opt && dir_opt[0]) {
1853
0
        dir = mp_get_user_path(vo, p->global, dir_opt);
1854
0
    } else {
1855
0
        dir = mp_find_user_file(vo, p->global, "cache", "");
1856
0
    }
1857
0
    if (!dir || !dir[0])
1858
0
        return;
1859
1860
0
    mp_mkdirp(dir);
1861
0
    *cache = (struct cache){
1862
0
        .log        = p->log,
1863
0
        .global     = p->global,
1864
0
        .dir        = dir,
1865
0
        .name       = name,
1866
0
        .size_limit = limit,
1867
0
        .cache = pl_cache_create(pl_cache_params(
1868
0
            .log = p->pllog,
1869
0
            .get = cache_load_obj,
1870
0
            .set = cache_save_obj,
1871
0
            .priv = cache
1872
0
        )),
1873
0
    };
1874
0
}
1875
1876
struct file_entry {
1877
    char *filepath;
1878
    size_t size;
1879
    time_t atime;
1880
};
1881
1882
static int compare_atime(const void *a, const void *b)
1883
0
{
1884
0
    return (((struct file_entry *)b)->atime - ((struct file_entry *)a)->atime);
1885
0
}
1886
1887
static void cache_uninit(struct priv *p, struct cache *cache)
1888
398
{
1889
398
    if (!cache->cache)
1890
398
        return;
1891
1892
0
    void *ta_ctx = talloc_new(NULL);
1893
0
    struct file_entry *files = NULL;
1894
0
    size_t num_files = 0;
1895
0
    mp_assert(cache->dir);
1896
0
    mp_assert(cache->name);
1897
1898
0
    DIR *d = opendir(cache->dir);
1899
0
    if (!d)
1900
0
        goto done;
1901
1902
0
    struct dirent *dir;
1903
0
    while ((dir = readdir(d)) != NULL) {
1904
0
        char *filepath = mp_path_join(ta_ctx, cache->dir, dir->d_name);
1905
0
        if (!filepath)
1906
0
            continue;
1907
0
        struct stat filestat;
1908
0
        if (stat(filepath, &filestat))
1909
0
            continue;
1910
0
        if (!S_ISREG(filestat.st_mode))
1911
0
            continue;
1912
0
        bstr fname = bstr0(dir->d_name);
1913
0
        if (!bstr_eatstart0(&fname, cache->name))
1914
0
            continue;
1915
0
        if (!bstr_eatstart0(&fname, "_"))
1916
0
            continue;
1917
0
        if (fname.len != 16) // %016x
1918
0
            continue;
1919
0
        MP_TARRAY_APPEND(ta_ctx, files, num_files,
1920
0
                         (struct file_entry){
1921
0
                             .filepath = filepath,
1922
0
                             .size     = filestat.st_size,
1923
0
                             .atime    = filestat.st_atime,
1924
0
                         });
1925
0
    }
1926
0
    closedir(d);
1927
1928
0
    if (!num_files)
1929
0
        goto done;
1930
1931
0
    qsort(files, num_files, sizeof(struct file_entry), compare_atime);
1932
1933
0
    time_t t = time(NULL);
1934
0
    size_t cache_size = 0;
1935
0
    size_t cache_limit = cache->size_limit ? cache->size_limit : SIZE_MAX;
1936
0
    for (int i = 0; i < num_files; i++) {
1937
        // Remove files that exceed the size limit but are older than one day.
1938
        // This allows for temporary maintaining a larger cache size while
1939
        // adjusting the configuration. The cache will be cleared the next day
1940
        // for unused entries. We don't need to be overly aggressive with cache
1941
        // cleaning; in most cases, it will not grow much, and in others, it may
1942
        // actually be useful to cache more.
1943
0
        cache_size += files[i].size;
1944
0
        double rel_use = difftime(t, files[i].atime);
1945
0
        if (cache_size > cache_limit && rel_use > 60 * 60 * 24) {
1946
0
            MP_VERBOSE(p, "Removing %s | size: %9zu bytes | last used: %9d seconds ago\n",
1947
0
                       files[i].filepath, files[i].size, (int)rel_use);
1948
0
            unlink(files[i].filepath);
1949
0
        }
1950
0
    }
1951
1952
0
done:
1953
0
    talloc_free(ta_ctx);
1954
0
    pl_cache_destroy(&cache->cache);
1955
0
}
1956
1957
static void uninit(struct vo *vo)
1958
199
{
1959
199
    struct priv *p = vo->priv;
1960
199
    pl_queue_destroy(&p->queue); // destroy this first
1961
1.19k
    for (int i = 0; i < MP_ARRAY_SIZE(p->osd_state.entries); i++)
1962
995
        pl_tex_destroy(p->gpu, &p->osd_state.entries[i].tex);
1963
199
    for (int i = 0; i < p->num_sub_tex; i++)
1964
0
        pl_tex_destroy(p->gpu, &p->sub_tex[i]);
1965
199
    for (int i = 0; i < p->num_user_hooks; i++)
1966
0
        pl_mpv_user_shader_destroy(&p->user_hooks[i].hook);
1967
1968
199
    if (vo->hwdec_devs) {
1969
0
        ra_hwdec_mapper_free(&p->hwdec_mapper);
1970
0
        ra_hwdec_ctx_uninit(&p->hwdec_ctx);
1971
0
        hwdec_devices_set_loader(vo->hwdec_devs, NULL, NULL);
1972
0
        hwdec_devices_destroy(vo->hwdec_devs);
1973
0
    }
1974
1975
199
    mp_assert(p->num_dr_buffers == 0);
1976
199
    mp_mutex_destroy(&p->dr_lock);
1977
1978
199
    cache_uninit(p, &p->shader_cache);
1979
199
    cache_uninit(p, &p->icc_cache);
1980
1981
199
    pl_lut_free(&p->next_opts->image_lut.lut);
1982
199
    pl_lut_free(&p->next_opts->lut.lut);
1983
199
    pl_lut_free(&p->next_opts->target_lut.lut);
1984
1985
199
    pl_icc_close(&p->icc_profile);
1986
199
    pl_renderer_destroy(&p->rr);
1987
1988
12.9k
    for (int i = 0; i < VO_PASS_PERF_MAX; ++i) {
1989
12.7k
        pl_shader_info_deref(&p->perf_fresh.info[i].shader);
1990
12.7k
        pl_shader_info_deref(&p->perf_redraw.info[i].shader);
1991
12.7k
    }
1992
1993
199
    pl_options_free(&p->pars);
1994
1995
199
    p->ra_ctx = NULL;
1996
199
    p->pllog = NULL;
1997
199
    p->gpu = NULL;
1998
199
    p->sw = NULL;
1999
199
    gpu_ctx_destroy(&p->context);
2000
199
}
2001
2002
static void load_hwdec_api(void *ctx, struct hwdec_imgfmt_request *params)
2003
0
{
2004
0
    vo_control(ctx, VOCTRL_LOAD_HWDEC_API, params);
2005
0
}
2006
2007
static int preinit(struct vo *vo)
2008
199
{
2009
199
    struct priv *p = vo->priv;
2010
199
    p->opts_cache = m_config_cache_alloc(p, vo->global, &gl_video_conf);
2011
199
    p->next_opts_cache = m_config_cache_alloc(p, vo->global, &gl_next_conf);
2012
199
    p->next_opts = p->next_opts_cache->opts;
2013
199
    p->video_eq = mp_csp_equalizer_create(p, vo->global);
2014
199
    p->global = vo->global;
2015
199
    p->log = vo->log;
2016
2017
199
    struct gl_video_opts *gl_opts = p->opts_cache->opts;
2018
199
    struct ra_ctx_opts *ctx_opts = mp_get_config_group(vo, vo->global, &ra_ctx_conf);
2019
199
    update_ra_ctx_options(vo, ctx_opts);
2020
199
    p->context = gpu_ctx_create(vo, ctx_opts);
2021
199
    talloc_free(ctx_opts);
2022
199
    if (!p->context)
2023
199
        goto err_out;
2024
    // For the time being
2025
0
    p->ra_ctx = p->context->ra_ctx;
2026
0
    p->pllog = p->context->pllog;
2027
0
    p->gpu = p->context->gpu;
2028
0
    p->sw = p->context->swapchain;
2029
0
    p->hwdec_ctx = (struct ra_hwdec_ctx) {
2030
0
        .log = p->log,
2031
0
        .global = p->global,
2032
0
        .ra_ctx = p->ra_ctx,
2033
0
    };
2034
2035
0
    vo->hwdec_devs = hwdec_devices_create();
2036
0
    hwdec_devices_set_loader(vo->hwdec_devs, load_hwdec_api, vo);
2037
0
    ra_hwdec_ctx_init(&p->hwdec_ctx, vo->hwdec_devs, gl_opts->hwdec_interop, false);
2038
0
    mp_mutex_init(&p->dr_lock);
2039
2040
0
    if (gl_opts->shader_cache)
2041
0
        cache_init(vo, &p->shader_cache, 10 << 20, gl_opts->shader_cache_dir);
2042
0
    if (gl_opts->icc_opts->cache)
2043
0
        cache_init(vo, &p->icc_cache, 20 << 20, gl_opts->icc_opts->cache_dir);
2044
2045
0
    pl_gpu_set_cache(p->gpu, p->shader_cache.cache);
2046
0
    p->rr = pl_renderer_create(p->pllog, p->gpu);
2047
0
    p->queue = pl_queue_create(p->gpu);
2048
0
    p->osd_fmt[SUBBITMAP_LIBASS] = pl_find_named_fmt(p->gpu, "r8");
2049
0
    p->osd_fmt[SUBBITMAP_BGRA] = pl_find_named_fmt(p->gpu, "bgra8");
2050
0
    p->osd_sync = 1;
2051
2052
0
    p->pars = pl_options_alloc(p->pllog);
2053
0
    update_render_options(vo);
2054
0
    return 0;
2055
2056
199
err_out:
2057
199
    uninit(vo);
2058
199
    return -1;
2059
199
}
2060
2061
static const struct pl_filter_config *map_scaler(struct priv *p,
2062
                                                 enum scaler_unit unit)
2063
0
{
2064
0
    const struct pl_filter_preset fixed_scalers[] = {
2065
0
        { "bilinear",       &pl_filter_bilinear },
2066
0
        { "bicubic_fast",   &pl_filter_bicubic },
2067
0
        { "nearest",        &pl_filter_nearest },
2068
0
        { "oversample",     &pl_filter_oversample },
2069
0
        {0},
2070
0
    };
2071
2072
0
    const struct pl_filter_preset fixed_frame_mixers[] = {
2073
0
        { "linear",         &pl_filter_bilinear },
2074
0
        { "oversample",     &pl_filter_oversample },
2075
0
        {0},
2076
0
    };
2077
2078
0
    const struct pl_filter_preset *fixed_presets =
2079
0
        unit == SCALER_TSCALE ? fixed_frame_mixers : fixed_scalers;
2080
2081
0
    const struct gl_video_opts *opts = p->opts_cache->opts;
2082
0
    const struct scaler_config *cfg = &opts->scaler[unit];
2083
0
    if (cfg->kernel.function == SCALER_INHERIT)
2084
0
        cfg = &opts->scaler[SCALER_SCALE];
2085
0
    const char *kernel_name = m_opt_choice_str(cfg->kernel.functions,
2086
0
                                               cfg->kernel.function);
2087
2088
0
    for (int i = 0; fixed_presets[i].name; i++) {
2089
0
        if (strcmp(kernel_name, fixed_presets[i].name) == 0)
2090
0
            return fixed_presets[i].filter;
2091
0
    }
2092
2093
    // Attempt loading filter preset first, fall back to raw filter function
2094
0
    struct scaler_params *par = &p->scalers[unit];
2095
0
    const struct pl_filter_preset *preset;
2096
0
    const struct pl_filter_function_preset *fpreset;
2097
0
    if ((preset = pl_find_filter_preset(kernel_name))) {
2098
0
        par->config = *preset->filter;
2099
0
    } else if ((fpreset = pl_find_filter_function_preset(kernel_name))) {
2100
0
        par->config = (struct pl_filter_config) {
2101
0
            .kernel = fpreset->function,
2102
0
            .params[0] = fpreset->function->params[0],
2103
0
            .params[1] = fpreset->function->params[1],
2104
0
        };
2105
0
    } else {
2106
0
        MP_ERR(p, "Failed mapping filter function '%s', no libplacebo analog?\n",
2107
0
               kernel_name);
2108
0
        return &pl_filter_bilinear;
2109
0
    }
2110
2111
0
    const struct pl_filter_function_preset *wpreset;
2112
0
    if ((wpreset = pl_find_filter_function_preset(
2113
0
             m_opt_choice_str(cfg->window.functions, cfg->window.function)))) {
2114
0
        par->config.window = wpreset->function;
2115
0
        par->config.wparams[0] = wpreset->function->params[0];
2116
0
        par->config.wparams[1] = wpreset->function->params[1];
2117
0
    }
2118
2119
0
    for (int i = 0; i < 2; i++) {
2120
0
        if (!isnan(cfg->kernel.params[i]))
2121
0
            par->config.params[i] = cfg->kernel.params[i];
2122
0
        if (!isnan(cfg->window.params[i]))
2123
0
            par->config.wparams[i] = cfg->window.params[i];
2124
0
    }
2125
2126
0
    par->config.clamp = cfg->clamp;
2127
0
    if (cfg->antiring > 0.0)
2128
0
        par->config.antiring = cfg->antiring;
2129
0
    if (cfg->kernel.blur > 0.0)
2130
0
        par->config.blur = cfg->kernel.blur;
2131
0
    if (cfg->kernel.taper > 0.0)
2132
0
        par->config.taper = cfg->kernel.taper;
2133
0
    if (cfg->radius > 0.0) {
2134
0
        if (par->config.kernel->resizable) {
2135
0
            par->config.radius = cfg->radius;
2136
0
        } else {
2137
0
            MP_WARN(p, "Filter radius specified but filter '%s' is not "
2138
0
                    "resizable, ignoring\n", kernel_name);
2139
0
        }
2140
0
    }
2141
2142
0
    return &par->config;
2143
0
}
2144
2145
static const struct pl_hook *load_hook(struct priv *p, const char *path)
2146
0
{
2147
0
    if (!path || !path[0])
2148
0
        return NULL;
2149
2150
0
    for (int i = 0; i < p->num_user_hooks; i++) {
2151
0
        if (strcmp(p->user_hooks[i].path, path) == 0)
2152
0
            return p->user_hooks[i].hook;
2153
0
    }
2154
2155
0
    char *fname = mp_get_user_path(NULL, p->global, path);
2156
0
    bstr shader = stream_read_file(fname, p, p->global, 1000000000); // 1GB
2157
0
    talloc_free(fname);
2158
2159
0
    const struct pl_hook *hook = NULL;
2160
0
    if (shader.len)
2161
0
        hook = pl_mpv_user_shader_parse(p->gpu, shader.start, shader.len);
2162
2163
0
    MP_TARRAY_APPEND(p, p->user_hooks, p->num_user_hooks, (struct user_hook) {
2164
0
        .path = talloc_strdup(p, path),
2165
0
        .hook = hook,
2166
0
    });
2167
2168
0
    return hook;
2169
0
}
2170
2171
static void update_icc_opts(struct priv *p, const struct mp_icc_opts *opts)
2172
0
{
2173
0
    if (!opts)
2174
0
        return;
2175
2176
0
    if (!opts->profile_auto && !p->icc_path) {
2177
        // Un-set any auto-loaded profiles if icc-profile-auto was disabled
2178
0
        update_icc(p, (bstr) {0});
2179
0
    }
2180
2181
0
    int s_r = 0, s_g = 0, s_b = 0;
2182
0
    gl_parse_3dlut_size(opts->size_str, &s_r, &s_g, &s_b);
2183
0
    p->icc_params = pl_icc_default_params;
2184
0
    p->icc_params.intent = opts->intent;
2185
0
    p->icc_params.size_r = s_r;
2186
0
    p->icc_params.size_g = s_g;
2187
0
    p->icc_params.size_b = s_b;
2188
0
    p->icc_params.cache = p->icc_cache.cache;
2189
2190
0
    if (!opts->profile || !opts->profile[0]) {
2191
        // No profile enabled, un-load any existing profiles
2192
0
        update_icc(p, (bstr) {0});
2193
0
        TA_FREEP(&p->icc_path);
2194
0
        return;
2195
0
    }
2196
2197
0
    if (p->icc_path && strcmp(opts->profile, p->icc_path) == 0)
2198
0
        return; // ICC profile hasn't changed
2199
2200
0
    char *fname = mp_get_user_path(NULL, p->global, opts->profile);
2201
0
    MP_VERBOSE(p, "Opening ICC profile '%s'\n", fname);
2202
0
    struct bstr icc = stream_read_file(fname, p, p->global, 100000000); // 100 MB
2203
0
    talloc_free(fname);
2204
0
    update_icc(p, icc);
2205
2206
    // Update cached path
2207
0
    talloc_replace(p, p->icc_path, opts->profile);
2208
0
}
2209
2210
static void update_lut(struct priv *p, struct user_lut *lut)
2211
0
{
2212
0
    if (!lut->opt || !lut->opt[0]) {
2213
0
        pl_lut_free(&lut->lut);
2214
0
        TA_FREEP(&lut->path);
2215
0
        return;
2216
0
    }
2217
2218
0
    if (lut->path && strcmp(lut->path, lut->opt) == 0)
2219
0
        return; // no change
2220
2221
    // Update cached path
2222
0
    pl_lut_free(&lut->lut);
2223
0
    talloc_replace(p, lut->path, lut->opt);
2224
2225
    // Load LUT file
2226
0
    char *fname = mp_get_user_path(NULL, p->global, lut->path);
2227
0
    MP_VERBOSE(p, "Loading custom LUT '%s'\n", fname);
2228
0
    const int lut_max_size = 1536 << 20; // 1.5 GiB, matches lut cache limit
2229
0
    struct bstr lutdata = stream_read_file(fname, NULL, p->global, lut_max_size);
2230
0
    if (!lutdata.len) {
2231
0
        MP_ERR(p, "Failed to read LUT data from %s, make sure it's a valid file "
2232
0
                  "and smaller or equal to %d bytes\n", fname, lut_max_size);
2233
0
    } else {
2234
0
        lut->lut = pl_lut_parse_cube(p->pllog, lutdata.start, lutdata.len);
2235
0
    }
2236
0
    talloc_free(fname);
2237
0
    talloc_free(lutdata.start);
2238
0
}
2239
2240
static void update_hook_opts_dynamic(struct priv *p, const struct pl_hook *hook,
2241
                                     const struct mp_image *mpi)
2242
0
{
2243
0
    float chroma_offset_x, chroma_offset_y;
2244
0
    pl_chroma_location_offset(mpi->params.chroma_location,
2245
0
                              &chroma_offset_x, &chroma_offset_y);
2246
0
    const struct {
2247
0
        const char *name;
2248
0
        double value;
2249
0
    } opts[] = {
2250
0
        {             "PTS", mpi->pts                           },
2251
0
        { "chroma_offset_x", chroma_offset_x                    },
2252
0
        { "chroma_offset_y", chroma_offset_y                    },
2253
0
        {        "min_luma", mpi->params.color.hdr.min_luma     },
2254
0
        {        "max_luma", mpi->params.color.hdr.max_luma     },
2255
0
        {         "max_cll", mpi->params.color.hdr.max_cll      },
2256
0
        {        "max_fall", mpi->params.color.hdr.max_fall     },
2257
0
        {     "scene_max_r", mpi->params.color.hdr.scene_max[0] },
2258
0
        {     "scene_max_g", mpi->params.color.hdr.scene_max[1] },
2259
0
        {     "scene_max_b", mpi->params.color.hdr.scene_max[2] },
2260
0
        {       "scene_avg", mpi->params.color.hdr.scene_avg    },
2261
0
        {        "max_pq_y", mpi->params.color.hdr.max_pq_y     },
2262
0
        {        "avg_pq_y", mpi->params.color.hdr.avg_pq_y     },
2263
0
    };
2264
2265
0
    for (int i = 0; i < hook->num_parameters; i++) {
2266
0
        const struct pl_hook_par *hp = &hook->parameters[i];
2267
0
        for (int n = 0; n < MP_ARRAY_SIZE(opts); n++) {
2268
0
            if (strcmp(hp->name, opts[n].name) != 0)
2269
0
                continue;
2270
2271
0
            switch (hp->type) {
2272
0
                case PL_VAR_FLOAT: hp->data->f = opts[n].value; break;
2273
0
                case PL_VAR_SINT:  hp->data->i = lrint(opts[n].value); break;
2274
0
                case PL_VAR_UINT:  hp->data->u = lrint(opts[n].value); break;
2275
0
            }
2276
0
        }
2277
0
    }
2278
0
}
2279
2280
static void update_hook_opts(struct priv *p, char **opts, const char *shaderpath,
2281
                             const struct pl_hook *hook)
2282
0
{
2283
0
    for (int i = 0; i < hook->num_parameters; i++) {
2284
0
        const struct pl_hook_par *hp = &hook->parameters[i];
2285
0
        memcpy(hp->data, &hp->initial, sizeof(*hp->data));
2286
0
    }
2287
2288
0
    if (!opts)
2289
0
        return;
2290
2291
0
    const char *basename = mp_basename(shaderpath);
2292
0
    struct bstr shadername;
2293
0
    if (!mp_splitext(basename, &shadername))
2294
0
        shadername = bstr0(basename);
2295
2296
0
    for (int n = 0; opts[n * 2]; n++) {
2297
0
        struct bstr k = bstr0(opts[n * 2 + 0]);
2298
0
        struct bstr v = bstr0(opts[n * 2 + 1]);
2299
0
        int pos;
2300
0
        if ((pos = bstrchr(k, '/')) >= 0) {
2301
0
            if (!bstr_equals(bstr_splice(k, 0, pos), shadername))
2302
0
                continue;
2303
0
            k = bstr_cut(k, pos + 1);
2304
0
        }
2305
2306
0
        for (int i = 0; i < hook->num_parameters; i++) {
2307
0
            const struct pl_hook_par *hp = &hook->parameters[i];
2308
0
            if (!bstr_equals0(k, hp->name) != 0)
2309
0
                continue;
2310
2311
0
            m_option_t opt = {
2312
0
                .name = hp->name,
2313
0
            };
2314
2315
0
            if (hp->names) {
2316
0
                for (int j = hp->minimum.i; j <= hp->maximum.i; j++) {
2317
0
                    if (bstr_equals0(v, hp->names[j])) {
2318
0
                        hp->data->i = j;
2319
0
                        goto next_hook;
2320
0
                    }
2321
0
                }
2322
0
            }
2323
2324
0
            switch (hp->type) {
2325
0
            case PL_VAR_FLOAT:
2326
0
                opt.type = &m_option_type_float;
2327
0
                opt.min = hp->minimum.f;
2328
0
                opt.max = hp->maximum.f;
2329
0
                break;
2330
0
            case PL_VAR_SINT:
2331
0
                opt.type = &m_option_type_int;
2332
0
                opt.min = hp->minimum.i;
2333
0
                opt.max = hp->maximum.i;
2334
0
                break;
2335
0
            case PL_VAR_UINT:
2336
0
                opt.type = &m_option_type_int;
2337
0
                opt.min = MPMIN(hp->minimum.u, INT_MAX);
2338
0
                opt.max = MPMIN(hp->maximum.u, INT_MAX);
2339
0
                break;
2340
0
            }
2341
2342
0
            if (!opt.type)
2343
0
                goto next_hook;
2344
2345
0
            opt.type->parse(p->log, &opt, k, v, hp->data);
2346
0
            goto next_hook;
2347
0
        }
2348
2349
0
    next_hook:;
2350
0
    }
2351
0
}
2352
2353
static void update_render_options(struct vo *vo)
2354
0
{
2355
0
    struct priv *p = vo->priv;
2356
0
    pl_options pars = p->pars;
2357
0
    const struct gl_video_opts *opts = p->opts_cache->opts;
2358
0
    pars->params.background_color[0] = opts->background_color.r / 255.0;
2359
0
    pars->params.background_color[1] = opts->background_color.g / 255.0;
2360
0
    pars->params.background_color[2] = opts->background_color.b / 255.0;
2361
0
    pars->params.background_transparency = 1 - opts->background_color.a / 255.0;
2362
0
    pars->params.skip_anti_aliasing = !opts->correct_downscaling;
2363
0
    pars->params.disable_linear_scaling = !opts->linear_downscaling && !opts->linear_upscaling;
2364
0
    pars->params.disable_fbos = opts->dumb_mode == 1;
2365
2366
0
#if PL_API_VER >= 346
2367
0
    int map_background_types[3] = {
2368
0
        PL_CLEAR_SKIP,  // BACKGROUND_NONE
2369
0
        PL_CLEAR_COLOR, // BACKGROUND_COLOR
2370
0
        PL_CLEAR_TILES, // BACKGROUND_TILES
2371
0
    };
2372
0
    pars->params.background = map_background_types[opts->background];
2373
0
    pars->params.border = map_background_types[p->next_opts->border_background];
2374
#else
2375
    pars->params.blend_against_tiles = opts->background == BACKGROUND_TILES;
2376
#endif
2377
0
    pars->params.tile_size = opts->background_tile_size * 2;
2378
0
    for (int i = 0; i < 2; ++i) {
2379
0
        pars->params.tile_colors[i][0] = opts->background_tile_color[i].r / 255.0f;
2380
0
        pars->params.tile_colors[i][1] = opts->background_tile_color[i].g / 255.0f;
2381
0
        pars->params.tile_colors[i][2] = opts->background_tile_color[i].b / 255.0f;
2382
0
    }
2383
2384
0
    pars->params.corner_rounding = p->next_opts->corner_rounding;
2385
0
    pars->params.correct_subpixel_offsets = !opts->scaler_resizes_only;
2386
2387
    // Map scaler options as best we can
2388
0
    pars->params.upscaler = map_scaler(p, SCALER_SCALE);
2389
0
    pars->params.downscaler = map_scaler(p, SCALER_DSCALE);
2390
0
    pars->params.plane_upscaler = map_scaler(p, SCALER_CSCALE);
2391
0
    pars->params.frame_mixer = opts->interpolation ? map_scaler(p, SCALER_TSCALE) : NULL;
2392
2393
    // Request as many frames as required from the decoder, depending on the
2394
    // speed VPS/FPS ratio libplacebo may need more frames. Request frames up to
2395
    // ratio of 1/2, but only if anti aliasing is enabled.
2396
0
    int req_frames = 2;
2397
0
    if (pars->params.frame_mixer) {
2398
0
        req_frames += ceilf(pars->params.frame_mixer->kernel->radius) *
2399
0
                      (pars->params.skip_anti_aliasing ? 1 : 2);
2400
0
    }
2401
0
    vo_set_queue_params(vo, 0, MPMIN(VO_MAX_REQ_FRAMES, req_frames));
2402
2403
0
    pars->params.deband_params = opts->deband ? &pars->deband_params : NULL;
2404
0
    pars->deband_params.iterations = opts->deband_opts->iterations;
2405
0
    pars->deband_params.radius = opts->deband_opts->range;
2406
0
    pars->deband_params.threshold = opts->deband_opts->threshold / 16.384;
2407
0
    pars->deband_params.grain = opts->deband_opts->grain / 8.192;
2408
2409
0
    pars->params.sigmoid_params = opts->sigmoid_upscaling ? &pars->sigmoid_params : NULL;
2410
0
    pars->sigmoid_params.center = opts->sigmoid_center;
2411
0
    pars->sigmoid_params.slope = opts->sigmoid_slope;
2412
2413
0
    pars->params.peak_detect_params = opts->tone_map.compute_peak >= 0 ? &pars->peak_detect_params : NULL;
2414
0
    pars->peak_detect_params.smoothing_period = opts->tone_map.decay_rate;
2415
0
    pars->peak_detect_params.scene_threshold_low = opts->tone_map.scene_threshold_low;
2416
0
    pars->peak_detect_params.scene_threshold_high = opts->tone_map.scene_threshold_high;
2417
0
    pars->peak_detect_params.percentile = opts->tone_map.peak_percentile;
2418
0
    pars->peak_detect_params.allow_delayed = p->next_opts->delayed_peak;
2419
2420
0
    const struct pl_tone_map_function * const tone_map_funs[] = {
2421
0
        [TONE_MAPPING_AUTO]     = &pl_tone_map_auto,
2422
0
        [TONE_MAPPING_CLIP]     = &pl_tone_map_clip,
2423
0
        [TONE_MAPPING_MOBIUS]   = &pl_tone_map_mobius,
2424
0
        [TONE_MAPPING_REINHARD] = &pl_tone_map_reinhard,
2425
0
        [TONE_MAPPING_HABLE]    = &pl_tone_map_hable,
2426
0
        [TONE_MAPPING_GAMMA]    = &pl_tone_map_gamma,
2427
0
        [TONE_MAPPING_LINEAR]   = &pl_tone_map_linear,
2428
0
        [TONE_MAPPING_SPLINE]   = &pl_tone_map_spline,
2429
0
        [TONE_MAPPING_BT_2390]  = &pl_tone_map_bt2390,
2430
0
        [TONE_MAPPING_BT_2446A] = &pl_tone_map_bt2446a,
2431
0
        [TONE_MAPPING_ST2094_40] = &pl_tone_map_st2094_40,
2432
0
        [TONE_MAPPING_ST2094_10] = &pl_tone_map_st2094_10,
2433
0
    };
2434
2435
0
    const struct pl_gamut_map_function * const gamut_modes[] = {
2436
0
        [GAMUT_AUTO]            = pl_color_map_default_params.gamut_mapping,
2437
0
        [GAMUT_CLIP]            = &pl_gamut_map_clip,
2438
0
        [GAMUT_PERCEPTUAL]      = &pl_gamut_map_perceptual,
2439
0
        [GAMUT_RELATIVE]        = &pl_gamut_map_relative,
2440
0
        [GAMUT_SATURATION]      = &pl_gamut_map_saturation,
2441
0
        [GAMUT_ABSOLUTE]        = &pl_gamut_map_absolute,
2442
0
        [GAMUT_DESATURATE]      = &pl_gamut_map_desaturate,
2443
0
        [GAMUT_DARKEN]          = &pl_gamut_map_darken,
2444
0
        [GAMUT_WARN]            = &pl_gamut_map_highlight,
2445
0
        [GAMUT_LINEAR]          = &pl_gamut_map_linear,
2446
0
    };
2447
2448
0
    pars->color_map_params.tone_mapping_function = tone_map_funs[opts->tone_map.curve];
2449
0
AV_NOWARN_DEPRECATED(
2450
0
    pars->color_map_params.tone_mapping_param = opts->tone_map.curve_param;
2451
0
    if (isnan(pars->color_map_params.tone_mapping_param)) // vo_gpu compatibility
2452
0
        pars->color_map_params.tone_mapping_param = 0.0;
2453
0
)
2454
0
    pars->color_map_params.inverse_tone_mapping = opts->tone_map.inverse;
2455
0
    pars->color_map_params.contrast_recovery = opts->tone_map.contrast_recovery;
2456
0
    pars->color_map_params.visualize_lut = opts->tone_map.visualize;
2457
0
    pars->color_map_params.contrast_smoothness = opts->tone_map.contrast_smoothness;
2458
0
    pars->color_map_params.gamut_mapping = gamut_modes[opts->tone_map.gamut_mode];
2459
2460
0
    pars->params.dither_params = NULL;
2461
0
    pars->params.error_diffusion = NULL;
2462
2463
0
    switch (opts->dither_algo) {
2464
0
    case DITHER_ERROR_DIFFUSION:
2465
0
        pars->params.error_diffusion = pl_find_error_diffusion_kernel(opts->error_diffusion);
2466
0
        if (!pars->params.error_diffusion) {
2467
0
            MP_WARN(p, "Could not find error diffusion kernel '%s', falling "
2468
0
                    "back to fruit.\n", opts->error_diffusion);
2469
0
        }
2470
0
        MP_FALLTHROUGH;
2471
0
    case DITHER_ORDERED:
2472
0
    case DITHER_FRUIT:
2473
0
        pars->params.dither_params = &pars->dither_params;
2474
0
        pars->dither_params.method = opts->dither_algo == DITHER_ORDERED
2475
0
                                ? PL_DITHER_ORDERED_FIXED
2476
0
                                : PL_DITHER_BLUE_NOISE;
2477
0
        pars->dither_params.lut_size = opts->dither_size;
2478
0
        pars->dither_params.temporal = opts->temporal_dither;
2479
0
        break;
2480
0
    }
2481
2482
0
    if (opts->dither_depth < 0) {
2483
0
        pars->params.dither_params = NULL;
2484
0
        pars->params.error_diffusion = NULL;
2485
0
    }
2486
2487
0
    update_icc_opts(p, opts->icc_opts);
2488
2489
0
    pars->params.num_hooks = 0;
2490
0
    const struct pl_hook *hook;
2491
0
    for (int i = 0; opts->user_shaders && opts->user_shaders[i]; i++) {
2492
0
        if ((hook = load_hook(p, opts->user_shaders[i]))) {
2493
0
            MP_TARRAY_APPEND(p, p->hooks, pars->params.num_hooks, hook);
2494
0
            update_hook_opts(p, opts->user_shader_opts, opts->user_shaders[i], hook);
2495
0
        }
2496
0
    }
2497
2498
0
    pars->params.hooks = p->hooks;
2499
2500
0
    MP_DBG(p, "Render options updated, resetting render state.\n");
2501
0
    p->want_reset = true;
2502
0
}
2503
2504
const struct vo_driver video_out_gpu_next = {
2505
    .description = "Video output based on libplacebo",
2506
    .name = "gpu-next",
2507
    .caps = VO_CAP_ROTATE90 |
2508
            VO_CAP_FILM_GRAIN |
2509
            VO_CAP_VFLIP |
2510
            0x0,
2511
    .preinit = preinit,
2512
    .query_format = query_format,
2513
    .reconfig = reconfig,
2514
    .control = control,
2515
    .get_image_ts = get_image,
2516
    .draw_frame = draw_frame,
2517
    .flip_page = flip_page,
2518
    .get_vsync = get_vsync,
2519
    .wait_events = wait_events,
2520
    .wakeup = wakeup,
2521
    .uninit = uninit,
2522
    .priv_size = sizeof(struct priv),
2523
};