/*
* Copyright (C) 2021 Niklas Haas
*
* This file is part of mpv.
*
* mpv is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* mpv is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with mpv. If not, see <http://www.gnu.org/licenses/>.
*/
#include <libplacebo/renderer.h>
#include <libplacebo/shaders/lut.h>
#include <libplacebo/utils/libav.h>
#include <libplacebo/utils/frame_queue.h>
#ifdef PL_HAVE_LCMS
#include <libplacebo/shaders/icc.h>
#endif
#include "config.h"
#include "common/common.h"
#include "options/m_config.h"
#include "options/path.h"
#include "osdep/io.h"
#include "stream/stream.h"
#include "video/mp_image.h"
#include "video/fmt-conversion.h"
#include "placebo/utils.h"
#include "gpu/context.h"
#include "gpu/video.h"
#include "gpu/video_shaders.h"
#include "sub/osd.h"
#include "gpu_next/context.h"
struct osd_entry {
pl_tex tex;
struct pl_overlay_part *parts;
int num_parts;
};
struct osd_state {
struct osd_entry entries[MAX_OSD_PARTS];
struct pl_overlay overlays[MAX_OSD_PARTS];
};
struct scaler_params {
struct pl_filter_config config;
struct pl_filter_function kernel;
struct pl_filter_function window;
};
struct user_hook {
char *path;
const struct pl_hook *hook;
};
struct user_lut {
char *opt;
char *path;
int type;
struct pl_custom_lut *lut;
};
struct priv {
struct mp_log *log;
struct mpv_global *global;
struct ra_ctx *ra_ctx;
struct gpu_ctx *context;
pl_log pllog;
pl_gpu gpu;
pl_renderer rr;
pl_queue queue;
pl_swapchain sw;
pl_fmt osd_fmt[SUBBITMAP_COUNT];
pl_tex *sub_tex;
int num_sub_tex;
struct mp_rect src, dst;
struct mp_osd_res osd_res;
struct osd_state osd_state;
uint64_t last_id;
double last_pts;
bool is_interpolated;
bool want_reset;
struct m_config_cache *opts_cache;
struct mp_csp_equalizer_state *video_eq;
struct pl_render_params params;
struct pl_deband_params deband;
struct pl_sigmoid_params sigmoid;
struct pl_color_adjustment color_adjustment;
struct pl_peak_detect_params peak_detect;
struct pl_color_map_params color_map;
struct pl_dither_params dither;
struct scaler_params scalers[SCALER_COUNT];
const struct pl_hook **hooks; // storage for `params.hooks`
const struct pl_filter_config *frame_mixer;
#ifdef PL_HAVE_LCMS
struct pl_icc_params icc;
struct pl_icc_profile icc_profile;
char *icc_path;
#endif
struct user_lut image_lut;
struct user_lut target_lut;
struct user_lut lut;
// Cached shaders, preserved across options updates
struct user_hook *user_hooks;
int num_user_hooks;
// Performance data of last frame
struct voctrl_performance_data perf;
int delayed_peak;
int inter_preserve;
int target_hint;
};
static void update_render_options(struct priv *p);
static void update_lut(struct priv *p, struct user_lut *lut);
// This struct is stored at the end of DR-allocated buffers, and serves to both
// detect such buffers and hold the reference to the actual GPU buffer.
struct dr_buf {
uint64_t sentinel[2];
pl_gpu gpu;
pl_buf buf;
};
static const uint64_t dr_magic[2] = { 0xc6e9222474db53ae, 0x9d49b2de6c3b563e };
static const size_t dr_align = offsetof(struct { char c; struct dr_buf dr; }, dr);
static inline struct dr_buf *dr_header(void *ptr, size_t size)
{
uintptr_t start = (uintptr_t) ptr + size - sizeof(struct dr_buf);
uintptr_t aligned = MP_ALIGN_DOWN(start, dr_align);
assert(aligned >= (uintptr_t) ptr);
return (struct dr_buf *) aligned;
}
static pl_buf get_dr_buf(struct mp_image *mpi)
{
if (!mpi->bufs[0] || mpi->bufs[0]->size < sizeof(struct dr_buf))
return NULL;
struct dr_buf *dr = dr_header(mpi->bufs[0]->data, mpi->bufs[0]->size);
if (memcmp(dr->sentinel, dr_magic, sizeof(dr_magic)) == 0)
return dr->buf;
return NULL;
}
static void free_dr_buf(void *opaque, uint8_t *data)
{
struct dr_buf *dr = opaque;
assert(memcmp(dr->sentinel, dr_magic, sizeof(dr_magic)) == 0);
// Can't use `&dr->buf` because it gets freed during `pl_buf_destroy`
pl_buf_destroy(dr->gpu, &(pl_buf) { dr->buf });
}
static struct mp_image *get_image(struct vo *vo, int imgfmt, int w, int h,
int stride_align)
{
struct priv *p = vo->priv;
pl_gpu gpu = p->gpu;
if (!gpu->limits.thread_safe || !gpu->limits.max_mapped_size)
return NULL;
int size = mp_image_get_alloc_size(imgfmt, w, h, stride_align);
if (size < 0)
return NULL;
pl_buf buf = pl_buf_create(gpu, &(struct pl_buf_params) {
.size = size + stride_align + sizeof(struct dr_buf) + dr_align,
.memory_type = PL_BUF_MEM_HOST,
.host_mapped = true,
});
if (!buf)
return NULL;
// Store the DR header at the end of the allocation
struct dr_buf *dr = dr_header(buf->data, buf->params.size);
memcpy(dr->sentinel, dr_magic, sizeof(dr_magic));
dr->gpu = gpu;
dr->buf = buf;
struct mp_image *mpi = mp_image_from_buffer(imgfmt, w, h, stride_align,
buf->data, buf->params.size,
dr, free_dr_buf);
if (!mpi) {
pl_buf_destroy(gpu, &buf);
return NULL;
}
return mpi;
}
static void write_overlays(struct vo *vo, struct mp_osd_res res, double pts,
int flags, struct osd_state *state,
struct pl_frame *frame, bool flip)
{
struct priv *p = vo->priv;
static const bool subfmt_all[SUBBITMAP_COUNT] = {
[SUBBITMAP_LIBASS] = true,
[SUBBITMAP_RGBA] = true,
};
struct sub_bitmap_list *subs = osd_render(vo->osd, res, pts, flags, subfmt_all);
frame->num_overlays = 0;
frame->overlays = state->overlays;
for (int n = 0; n < subs->num_items; n++) {
const struct sub_bitmaps *item = subs->items[n];
if (!item->num_parts || !item->packed)
continue;
struct osd_entry *entry = &state->entries[item->render_index];
pl_fmt tex_fmt = p->osd_fmt[item->format];
if (!entry->tex)
MP_TARRAY_POP(p->sub_tex, p->num_sub_tex, &entry->tex);
bool ok = pl_tex_recreate(p->gpu, &entry->tex, &(struct pl_tex_params) {
.format = tex_fmt,
.w = MPMAX(item->packed_w, entry->tex ? entry->tex->params.w : 0),
.h = MPMAX(item->packed_h, entry->tex ? entry->tex->params.h : 0),
.host_writable = true,
.sampleable = true,
});
if (!ok) {
MP_ERR(vo, "Failed recreating OSD texture!\n");
break;
}
ok = pl_tex_upload(p->gpu, &(struct pl_tex_transfer_params) {
.tex = entry->tex,
.rc = { .x1 = item->packed_w, .y1 = item->packed_h, },
.stride_w = item->packed->stride[0] / tex_fmt->texel_size,
.ptr = item->packed->planes[0],
});
if (!ok) {
MP_ERR(vo, "Failed uploading OSD texture!\n");
break;
}
entry->num_parts = 0;
for (int i = 0; i < item->num_parts; i++) {
const struct sub_bitmap *b = &item->parts[i];
uint32_t c = b->libass.color;
struct pl_overlay_part part = {
.src = { b->src_x, b->src_y, b->src_x + b->w, b-
|