1
0
mirror of https://github.com/mpv-player/mpv synced 2025-02-18 05:37:04 +00:00
mpv/video/out/vo_gpu_next.c
Niklas Haas 4991ffa859 vo_gpu_next: implement VOCTRL_SCREENSHOT
Somewhat annoying but still relatively straightforward. There are
several ways to approach this, but I settled on reusing the pl_queue as
a cheap way to get access to the currently mapped frame. This saves us
from having to process `vo_frame` at all, and also avoids any overhead
from re-uploading the same frame twice.

(However maybe there's some circumstance in which `vo_frame` needs to be
queried/updated first, to get a screenshot of the correct frame? I'm not
sure.)

I also had the option of going with either pl_render_image() on the
extract pl_frame, or just calling pl_render_image_mix directly on it. I
went for the latter, because in the optimal case, this allows the
rendered frame to be directly retrieved from the cache, actually
entirely avoiding any sort of recompute overhead. This makes e.g. ctrl+s
during playback essentially free. (Except for the download cost,
obviously)

It would be even neater if we could make this VOCTRL asynchronous and
thereby leverage libplacebo's asynchronous download capabilities. But oh
well. That will have to wait for a sufficiently rainy day.

Closes #9388
2021-11-28 11:58:53 +01:00

1494 lines
49 KiB
C

/*
* 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->src_y + b->h },
.dst = { b->x, b->y, b->x + b->dw, b->y + b->dh },
.color = {
(c >> 24) / 255.0,
((c >> 16) & 0xFF) / 255.0,
((c >> 8) & 0xFF) / 255.0,
1.0 - (c & 0xFF) / 255.0,
}
};
if (flip) {
assert(frame->crop.y0 > frame->crop.y1);
part.dst.y0 = frame->crop.y0 - part.dst.y0;
part.dst.y1 = frame->crop.y0 - part.dst.y1;
}
MP_TARRAY_APPEND(p, entry->parts, entry->num_parts, part);
}
struct pl_overlay *ol = &state->overlays[frame->num_overlays++];
*ol = (struct pl_overlay) {
.tex = entry->tex,
.parts = entry->parts,
.num_parts = entry->num_parts,
.color = frame->color,
};
switch (item->format) {
case SUBBITMAP_RGBA:
ol->mode = PL_OVERLAY_NORMAL;
ol->repr.alpha = PL_ALPHA_PREMULTIPLIED;
break;
case SUBBITMAP_LIBASS:
ol->mode = PL_OVERLAY_MONOCHROME;
ol->repr.alpha = PL_ALPHA_INDEPENDENT;
break;
}
}
talloc_free(subs);
}
struct frame_priv {
struct vo *vo;
struct osd_state subs;
};
static int plane_data_from_imgfmt(struct pl_plane_data out_data[4],
struct pl_bit_encoding *out_bits,
enum mp_imgfmt imgfmt)
{
struct mp_imgfmt_desc desc = mp_imgfmt_get_desc(imgfmt);
if (!desc.num_planes || !(desc.flags & MP_IMGFLAG_HAS_COMPS))
return 0;
if (desc.flags & MP_IMGFLAG_HWACCEL)
return 0; // HW-accelerated frames need to be mapped differently
if (!(desc.flags & MP_IMGFLAG_NE))
return 0; // GPU endianness follows the host's
if (desc.flags & MP_IMGFLAG_PAL)
return 0; // Palette formats (currently) not supported in libplacebo
if ((desc.flags & MP_IMGFLAG_TYPE_FLOAT) && (desc.flags & MP_IMGFLAG_YUV))
return 0; // Floating-point YUV (currently) unsupported
bool any_padded = false;
for (int p = 0; p < desc.num_planes; p++) {
struct pl_plane_data *data = &out_data[p];
struct mp_imgfmt_comp_desc sorted[MP_NUM_COMPONENTS];
int num_comps = 0;
for (int c = 0; c < mp_imgfmt_desc_get_num_comps(&desc); c++) {
if (desc.comps[c].plane != p)
continue;
data->component_map[num_comps] = c;
sorted[num_comps] = desc.comps[c];
num_comps++;
// Sort components by offset order, while keeping track of the
// semantic mapping in `data->component_map`
for (int i = num_comps - 1; i > 0; i--) {
if (sorted[i].offset >= sorted[i - 1].offset)
break;
MPSWAP(struct mp_imgfmt_comp_desc, sorted[i], sorted[i - 1]);
MPSWAP(int, data->component_map[i], data->component_map[i - 1]);
}
}
uint64_t total_bits = 0;
// Fill in the pl_plane_data fields for each component
memset(data->component_size, 0, sizeof(data->component_size));
for (int c = 0; c < num_comps; c++) {
data->component_size[c] = sorted[c].size;
data->component_pad[c] = sorted[c].offset - total_bits;
total_bits += data->component_pad[c] + data->component_size[c];
any_padded |= sorted[c].pad;
// Ignore bit encoding of alpha channel
if (!out_bits || data->component_map[c] == PL_CHANNEL_A)
continue;
struct pl_bit_encoding bits = {
.sample_depth = data->component_size[c],
.color_depth = sorted[c].size - abs(sorted[c].pad),
.bit_shift = MPMAX(sorted[c].pad, 0),
};
if (p == 0 && c == 0) {
*out_bits = bits;
} else {
if (!pl_bit_encoding_equal(out_bits, &bits)) {
// Bit encoding differs between components/planes,
// cannot handle this
*out_bits = (struct pl_bit_encoding) {0};
out_bits = NULL;
}
}
}
if (total_bits % 8)
return 0; // pixel size is not byte-aligned
data->pixel_stride = total_bits / 8;
data->type = (desc.flags & MP_IMGFLAG_TYPE_FLOAT)
? PL_FMT_FLOAT
: PL_FMT_UNORM;
}
if (any_padded && !out_bits)
return 0; // can't handle padded components without `pl_bit_encoding`
return desc.num_planes;
}
static bool map_frame(pl_gpu gpu, pl_tex *tex, const struct pl_source_frame *src,
struct pl_frame *frame)
{
struct mp_image *mpi = src->frame_data;
const struct mp_image_params *par = &mpi->params;
struct frame_priv *fp = mpi->priv;
struct pl_plane_data data[4] = {0};
struct vo *vo = fp->vo;
struct priv *p = vo->priv;
// TODO: implement support for hwdec wrappers
*frame = (struct pl_frame) {
.num_planes = mpi->num_planes,
.color = {
.primaries = mp_prim_to_pl(par->color.primaries),
.transfer = mp_trc_to_pl(par->color.gamma),
.light = mp_light_to_pl(par->color.light),
.sig_peak = par->color.sig_peak,
},
.repr = {
.sys = mp_csp_to_pl(par->color.space),
.levels = mp_levels_to_pl(par->color.levels),
.alpha = mp_alpha_to_pl(par->alpha),
},
.profile = {
.data = mpi->icc_profile ? mpi->icc_profile->data : NULL,
.len = mpi->icc_profile ? mpi->icc_profile->size : 0,
},
.rotation = par->rotate / 90,
.user_data = mpi,
};
// mp_image, like AVFrame, likes communicating RGB/XYZ/YCbCr status
// implicitly via the image format, rather than the actual tagging.
switch (mp_imgfmt_get_forced_csp(par->imgfmt)) {
case MP_CSP_RGB:
frame->repr.sys = PL_COLOR_SYSTEM_RGB;
frame->repr.levels = PL_COLOR_LEVELS_FULL;
break;
case MP_CSP_XYZ:
frame->repr.sys = PL_COLOR_SYSTEM_XYZ;
break;
case MP_CSP_AUTO:
if (!frame->repr.sys)
frame->repr.sys = pl_color_system_guess_ycbcr(par->w, par->h);
break;
default: break;
}
enum pl_chroma_location chroma = mp_chroma_to_pl(par->chroma_location);
int planes = plane_data_from_imgfmt(data, &frame->repr.bits, mpi->imgfmt);
for (int n = 0; n < planes; n++) {
data[n].width = mp_image_plane_w(mpi, n);
data[n].height = mp_image_plane_h(mpi, n);
data[n].row_stride = mpi->stride[n];
data[n].pixels = mpi->planes[n];
pl_buf buf = get_dr_buf(mpi);
if (buf) {
data[n].pixels = NULL;
data[n].buf = buf;
data[n].buf_offset = mpi->planes[n] - buf->data;
} else if (gpu->limits.callbacks) {
data[n].callback = talloc_free;
data[n].priv = mp_image_new_ref(mpi);
}
struct pl_plane *plane = &frame->planes[n];
if (!pl_upload_plane(gpu, plane, &tex[n], &data[n])) {
MP_ERR(vo, "Failed uploading frame!\n");
talloc_free(data[n].priv);
return false;
}
if (mpi->fmt.xs[n] || mpi->fmt.ys[n])
pl_chroma_location_offset(chroma, &plane->shift_x, &plane->shift_y);
}
// Compute a unique signature for any attached ICC profile. Wasteful in
// theory if the ICC profile is the same for multiple frames, but in
// practice ICC profiles are overwhelmingly going to be attached to
// still images so it shouldn't matter.
pl_icc_profile_compute_signature(&frame->profile);
// Generate subtitles for this frame
struct mp_osd_res vidres = {
.w = mpi->w, .h = mpi->h,
// compensate for anamorphic sources (render subtitles as normal)
.display_par = (float) par->p_h / par->p_w,
};
write_overlays(vo, vidres, mpi->pts, OSD_DRAW_SUB_ONLY, &fp->subs, frame, false);
// Update LUT attached to this frame
update_lut(p, &p->image_lut);
frame->lut = p->image_lut.lut;
frame->lut_type = p->image_lut.type;
return true;
}
static void unmap_frame(pl_gpu gpu, struct pl_frame *frame,
const struct pl_source_frame *src)
{
struct mp_image *mpi = src->frame_data;
struct frame_priv *fp = mpi->priv;
struct priv *p = fp->vo->priv;
for (int i = 0; i < MP_ARRAY_SIZE(fp->subs.entries); i++) {
pl_tex tex = fp->subs.entries[i].tex;
if (tex)
MP_TARRAY_APPEND(p, p->sub_tex, p->num_sub_tex, tex);
}
talloc_free(mpi);
}
static void discard_frame(const struct pl_source_frame *src)
{
struct mp_image *mpi = src->frame_data;
talloc_free(mpi);
}
static struct pl_swapchain_colors get_csp_hint(struct vo *vo, struct mp_image *mpi)
{
struct priv *p = vo->priv;
const struct gl_video_opts *opts = p->opts_cache->opts;
struct pl_swapchain_colors hint = {
.primaries = mp_prim_to_pl(mpi->params.color.primaries),
.transfer = mp_trc_to_pl(mpi->params.color.gamma),
};
// Respect target color space overrides
if (opts->target_prim)
hint.primaries = mp_prim_to_pl(opts->target_prim);
if (opts->target_trc)
hint.transfer = mp_prim_to_pl(opts->target_trc);
for (int i = 0; i < mpi->num_ff_side_data; i++) {
void *data = mpi->ff_side_data[i].buf->data;
switch (mpi->ff_side_data[i].type) {
case AV_FRAME_DATA_CONTENT_LIGHT_LEVEL: {
const AVContentLightMetadata *clm = data;
hint.hdr.max_cll = clm->MaxCLL;
hint.hdr.max_fall = clm->MaxFALL;
break;
}
case AV_FRAME_DATA_MASTERING_DISPLAY_METADATA: {
const AVMasteringDisplayMetadata *mdm = data;
if (mdm->has_luminance) {
hint.hdr.min_luma = av_q2d(mdm->min_luminance);
hint.hdr.max_luma = av_q2d(mdm->max_luminance);
}
if (mdm->has_primaries) {
hint.hdr.prim.red.x = av_q2d(mdm->display_primaries[0][0]);
hint.hdr.prim.red.y = av_q2d(mdm->display_primaries[0][1]);
hint.hdr.prim.green.x = av_q2d(mdm->display_primaries[1][0]);
hint.hdr.prim.green.y = av_q2d(mdm->display_primaries[1][1]);
hint.hdr.prim.blue.x = av_q2d(mdm->display_primaries[2][0]);
hint.hdr.prim.blue.y = av_q2d(mdm->display_primaries[2][1]);
hint.hdr.prim.white.x = av_q2d(mdm->white_point[0]);
hint.hdr.prim.white.y = av_q2d(mdm->white_point[1]);
}
break;
}
default: break;
}
}
return hint;
}
static void info_callback(void *priv, const struct pl_render_info *info)
{
struct vo *vo = priv;
struct priv *p = vo->priv;
int index;
struct mp_frame_perf *frame;
switch (info->stage) {
case PL_RENDER_STAGE_FRAME:
if (info->index > VO_PASS_PERF_MAX)
return; // silently ignore clipped passes, whatever
frame = &p->perf.fresh;
index = info->index;
break;
case PL_RENDER_STAGE_BLEND:
frame = &p->perf.redraw;
index = 0; // ignore blended frame count
break;
default: abort();
}
struct mp_pass_perf *perf = &frame->perf[index];
const struct pl_dispatch_info *pass = info->pass;
assert(VO_PERF_SAMPLE_COUNT >= MP_ARRAY_SIZE(pass->samples));
memcpy(perf->samples, pass->samples, pass->num_samples * sizeof(pass->samples[0]));
perf->count = pass->num_samples;
perf->last = pass->last;
perf->peak = pass->peak;
perf->avg = pass->average;
talloc_free(frame->desc[index]);
frame->desc[index] = talloc_strdup(p, pass->shader->description);
frame->count = index + 1;
}
static void update_options(struct priv *p)
{
if (m_config_cache_update(p->opts_cache))
update_render_options(p);
update_lut(p, &p->lut);
p->params.lut = p->lut.lut;
p->params.lut_type = p->lut.type;
// Update equalizer state
struct mp_csp_params cparams = MP_CSP_PARAMS_DEFAULTS;
mp_csp_equalizer_state_get(p->video_eq, &cparams);
p->color_adjustment = pl_color_adjustment_neutral;
p->color_adjustment.brightness = cparams.brightness;
p->color_adjustment.contrast = cparams.contrast;
p->color_adjustment.hue = cparams.hue;
p->color_adjustment.saturation = cparams.saturation;
p->color_adjustment.gamma = cparams.gamma;
}
static void apply_target_options(struct priv *p, struct pl_frame *target)
{
update_lut(p, &p->target_lut);
target->lut = p->target_lut.lut;
target->lut_type = p->target_lut.type;
#ifdef PL_HAVE_LCMS
target->profile = p->icc_profile;
#endif
// Colorspace overrides
const struct gl_video_opts *opts = p->opts_cache->opts;
if (opts->target_prim)
target->color.primaries = mp_prim_to_pl(opts->target_prim);
if (opts->target_trc)
target->color.transfer = mp_trc_to_pl(opts->target_trc);
if (opts->target_peak)
target->color.sig_peak = opts->target_peak;
if (opts->dither_depth > 0) {
struct pl_bit_encoding *tbits = &target->repr.bits;
tbits->color_depth += opts->dither_depth - tbits->sample_depth;
tbits->sample_depth = opts->dither_depth;
}
}
static void apply_crop(struct pl_frame *frame, struct mp_rect crop,
int width, int height)
{
frame->crop = (struct pl_rect2df) {
.x0 = crop.x0,
.y0 = crop.y0,
.x1 = crop.x1,
.y1 = crop.y1,
};
// mpv gives us rotated/flipped rects, libplacebo expects unrotated
pl_rect2df_rotate(&frame->crop, -frame->rotation);
if (frame->crop.x1 < frame->crop.x0) {
frame->crop.x0 = width - frame->crop.x0;
frame->crop.x1 = width - frame->crop.x1;
}
if (frame->crop.y1 < frame->crop.y0) {
frame->crop.y0 = height - frame->crop.y0;
frame->crop.y1 = height - frame->crop.y1;
}
}
static void draw_frame(struct vo *vo, struct vo_frame *frame)
{
struct priv *p = vo->priv;
pl_gpu gpu = p->gpu;
update_options(p);
p->params.info_callback = info_callback;
p->params.info_priv = vo;
// Push all incoming frames into the frame queue
for (int n = 0; n < frame->num_frames; n++) {
int id = frame->frame_id + n;
if (id <= p->last_id)
continue; // ignore already seen frames
if (p->want_reset) {
pl_renderer_flush_cache(p->rr);
pl_queue_reset(p->queue);
p->last_pts = 0.0;
p->want_reset = false;
}
struct mp_image *mpi = mp_image_new_ref(frame->frames[n]);
struct frame_priv *fp = talloc_zero(mpi, struct frame_priv);
mpi->priv = fp;
fp->vo = vo;
pl_queue_push(p->queue, &(struct pl_source_frame) {
.pts = mpi->pts,
.frame_data = mpi,
.map = map_frame,
.unmap = unmap_frame,
.discard = discard_frame,
});
p->last_id = id;
}
if (p->target_hint && frame->current) {
struct pl_swapchain_colors hint = get_csp_hint(vo, frame->current);
pl_swapchain_colorspace_hint(p->sw, &hint);
} else if (!p->target_hint) {
pl_swapchain_colorspace_hint(p->sw, NULL);
}
const struct gl_video_opts *opts = p->opts_cache->opts;
double vsync_offset = opts->interpolation ? frame->vsync_offset : 0;
struct pl_swapchain_frame swframe;
struct ra_swapchain *sw = p->ra_ctx->swapchain;
bool should_draw = sw->fns->start_frame(sw, NULL); // for wayland logic
if (!should_draw || !pl_swapchain_start_frame(p->sw, &swframe)) {
// Advance the queue state to the current PTS to discard unused frames
pl_queue_update(p->queue, NULL, pl_queue_params(
.pts = frame->current->pts + vsync_offset,
.radius = pl_frame_mix_radius(&p->params),
));
return;
}
bool valid = false;
p->is_interpolated = false;
// Calculate target
struct pl_frame target;
pl_frame_from_swapchain(&target, &swframe);
apply_target_options(p, &target);
write_overlays(vo, p->osd_res, 0, OSD_DRAW_OSD_ONLY, &p->osd_state, &target, swframe.flipped);
apply_crop(&target, p->dst, swframe.fbo->params.w, swframe.fbo->params.h);
if (swframe.flipped)
MPSWAP(float, target.crop.y0, target.crop.y1);
struct pl_frame_mix mix = {0};
if (frame->current) {
// Update queue state
struct pl_queue_params qparams = {
.pts = frame->current->pts + vsync_offset,
.radius = pl_frame_mix_radius(&p->params),
.vsync_duration = frame->vsync_interval,
.frame_duration = frame->ideal_frame_duration,
.interpolation_threshold = opts->interpolation_threshold,
};
// mpv likes to generate sporadically jumping PTS shortly after
// initialization, but pl_queue does not like these. Hard-clamp as
// a simple work-around.
qparams.pts = p->last_pts = MPMAX(qparams.pts, p->last_pts);
switch (pl_queue_update(p->queue, &mix, &qparams)) {
case PL_QUEUE_ERR:
MP_ERR(vo, "Failed updating frames!\n");
goto done;
case PL_QUEUE_EOF:
abort(); // we never signal EOF
case PL_QUEUE_MORE:
case PL_QUEUE_OK:
break;
}
// Update source crop on all existing frames. We technically own the
// `pl_frame` struct so this is kosher. This could be avoided by
// instead flushing the queue on resizes, but doing it this way avoids
// unnecessarily re-uploading frames.
for (int i = 0; i < mix.num_frames; i++) {
apply_crop((struct pl_frame *) mix.frames[i], p->src,
vo->params->w, vo->params->h);
}
}
#if PL_API_VER >= 179
bool will_redraw = frame->display_synced && frame->num_vsyncs > 1;
bool cache_frame = will_redraw || frame->still;
p->params.skip_caching_single_frame = !cache_frame;
#endif
p->params.preserve_mixing_cache = p->inter_preserve && !frame->still;
p->params.allow_delayed_peak_detect = p->delayed_peak;
p->params.frame_mixer = frame->still ? NULL : p->frame_mixer;
// Render frame
if (!pl_render_image_mix(p->rr, &mix, &target, &p->params)) {
MP_ERR(vo, "Failed rendering frame!\n");
goto done;
}
p->is_interpolated = mix.num_frames > 1;
valid = true;
// fall through
done:
if (!valid) // clear with purple to indicate error
pl_tex_clear(gpu, swframe.fbo, (float[4]){ 0.5, 0.0, 1.0, 1.0 });
if (!pl_swapchain_submit_frame(p->sw))
MP_ERR(vo, "Failed presenting frame!\n");
}
static void flip_page(struct vo *vo)
{
struct priv *p = vo->priv;
struct ra_swapchain *sw = p->ra_ctx->swapchain;
sw->fns->swap_buffers(sw);
}
static void get_vsync(struct vo *vo, struct vo_vsync_info *info)
{
struct priv *p = vo->priv;
struct ra_swapchain *sw = p->ra_ctx->swapchain;
if (sw->fns->get_vsync)
sw->fns->get_vsync(sw, info);
}
static int query_format(struct vo *vo, int format)
{
struct priv *p = vo->priv;
struct pl_bit_encoding bits;
struct pl_plane_data data[4];
int planes = plane_data_from_imgfmt(data, &bits, format);
if (!planes)
return false;
for (int i = 0; i < planes; i++) {
if (!pl_plane_find_fmt(p->gpu, NULL, &data[i]))
return false;
}
return true;
}
static void resize(struct vo *vo)
{
struct priv *p = vo->priv;
vo_get_src_dst_rects(vo, &p->src, &p->dst, &p->osd_res);
gpu_ctx_resize(p->context, vo->dwidth, vo->dheight);
vo->want_redraw = true;
}
static int reconfig(struct vo *vo, struct mp_image_params *params)
{
struct priv *p = vo->priv;
if (!p->ra_ctx->fns->reconfig(p->ra_ctx))
return -1;
resize(vo);
return 0;
}
static bool update_auto_profile(struct priv *p, int *events)
{
#ifdef PL_HAVE_LCMS
const struct gl_video_opts *opts = p->opts_cache->opts;
if (!opts->icc_opts || !opts->icc_opts->profile_auto || p->icc_path)
return false;
MP_VERBOSE(p, "Querying ICC profile...\n");
bstr icc = {0};
int r = p->ra_ctx->fns->control(p->ra_ctx, events, VOCTRL_GET_ICC_PROFILE, &icc);
if (r != VO_NOTAVAIL) {
if (r == VO_FALSE) {
MP_WARN(p, "Could not retrieve an ICC profile.\n");
} else if (r == VO_NOTIMPL) {
MP_ERR(p, "icc-profile-auto not implemented on this platform.\n");
}
talloc_free((void *) p->icc_profile.data);
p->icc_profile.data = icc.start;
p->icc_profile.len = icc.len;
pl_icc_profile_compute_signature(&p->icc_profile);
return true;
}
#endif // PL_HAVE_LCMS
return false;
}
static void video_screenshot(struct vo *vo, struct voctrl_screenshot *args)
{
struct priv *p = vo->priv;
pl_gpu gpu = p->gpu;
pl_tex fbo = NULL;
args->res = NULL;
update_options(p);
p->params.info_callback = NULL;
p->params.skip_caching_single_frame = true;
p->params.preserve_mixing_cache = false;
p->params.allow_delayed_peak_detect = false;
p->params.frame_mixer = NULL;
// Retrieve the current frame from the frame queue
struct pl_frame_mix mix;
enum pl_queue_status status;
status = pl_queue_update(p->queue, &mix, pl_queue_params(.pts = p->last_pts));
assert(status != PL_QUEUE_EOF);
if (status == PL_QUEUE_ERR) {
MP_ERR(vo, "Unknown error occured while trying to take screenshot!\n");
return;
}
if (status == PL_QUEUE_MORE || !mix.num_frames) {
MP_ERR(vo, "No frames available to take screenshot of? Open issue\n");
return;
}
// Passing an interpolation radius of 0 guarantees that the first frame in
// the resulting mix is the correct frame for this PTS
struct pl_frame *image = (struct pl_frame *) mix.frames[0];
struct mp_image *mpi = image->user_data;
int orig_overlays = image->num_overlays;
if (!args->subs)
image->num_overlays = 0;
struct mp_rect src = p->src, dst = p->dst;
struct mp_osd_res osd = p->osd_res;
if (!args->scaled) {
src = dst = (struct mp_rect) {0, 0, mpi->params.w, mpi->params.h};
osd = (struct mp_osd_res) {
.w = mpi->params.w,
.h = mpi->params.h,
.display_par = 1.0,
};
}
// Create target FBO, try high bit depth first
int mpfmt;
for (int depth = args->high_bit_depth ? 16 : 8; depth; depth -= 8) {
mpfmt = depth == 16 ? IMGFMT_RGBA64 : IMGFMT_RGBA;
pl_fmt fmt = pl_find_fmt(gpu, PL_FMT_UNORM, 4, depth, depth,
PL_FMT_CAP_RENDERABLE | PL_FMT_CAP_HOST_READABLE);
if (!fmt)
continue;
fbo = pl_tex_create(gpu, pl_tex_params(
.w = osd.w,
.h = osd.h,
.format = fmt,
.blit_dst = true,
.renderable = true,
.host_readable = true,
.storable = fmt->caps & PL_FMT_CAP_STORABLE,
));
if (fbo)
break;
}
if (!fbo) {
MP_ERR(vo, "Failed creating target FBO for screenshot!\n");
goto done;
}
struct pl_frame target = {
.num_planes = 1,
.planes[0] = {
.texture = fbo,
.components = 4,
.component_mapping = {0, 1, 2, 3},
},
};
apply_target_options(p, &target);
apply_crop(image, src, mpi->params.w, mpi->params.h);
apply_crop(&target, dst, fbo->params.w, fbo->params.h);
if (args->osd)
write_overlays(vo, osd, 0, OSD_DRAW_OSD_ONLY, &p->osd_state, &target, false);
if (!pl_render_image_mix(p->rr, &mix, &target, &p->params)) {
MP_ERR(vo, "Failed rendering frame!\n");
goto done;
}
args->res = mp_image_alloc(mpfmt, fbo->params.w, fbo->params.h);
if (!args->res)
goto done;
bool ok = pl_tex_download(gpu, pl_tex_transfer_params(
.tex = fbo,
.ptr = args->res->planes[0],
.row_pitch = args->res->stride[0],
));
if (!ok)
TA_FREEP(&args->res);
// fall through
done:
pl_tex_destroy(gpu, &fbo);
image->num_overlays = orig_overlays;
}
static int control(struct vo *vo, uint32_t request, void *data)
{
struct priv *p = vo->priv;
switch (request) {
case VOCTRL_SET_PANSCAN:
pl_renderer_flush_cache(p->rr); // invalidate source crop
resize(vo);
// fall through
case VOCTRL_SET_EQUALIZER:
case VOCTRL_PAUSE:
if (p->is_interpolated)
vo->want_redraw = true;
return VO_TRUE;
case VOCTRL_UPDATE_RENDER_OPTS: {
m_config_cache_update(p->opts_cache);
const struct gl_video_opts *opts = p->opts_cache->opts;
p->ra_ctx->opts.want_alpha = opts->alpha_mode == ALPHA_YES;
if (p->ra_ctx->fns->update_render_opts)
p->ra_ctx->fns->update_render_opts(p->ra_ctx);
update_render_options(p);
vo->want_redraw = true;
// Also re-query the auto profile, in case `update_render_options`
// unloaded a manually specified icc profile in favor of
// icc-profile-auto
int events = 0;
update_auto_profile(p, &events);
vo_event(vo, events);
return VO_TRUE;
}
case VOCTRL_RESET:
// Defer until the first new frame (unique ID) actually arrives
p->want_reset = true;
return VO_TRUE;
case VOCTRL_PERFORMANCE_DATA:
*(struct voctrl_performance_data *) data = p->perf;
return true;
case VOCTRL_SCREENSHOT:
video_screenshot(vo, data);
return true;
}
int events = 0;
int r = p->ra_ctx->fns->control(p->ra_ctx, &events, request, data);
if (events & VO_EVENT_ICC_PROFILE_CHANGED) {
if (update_auto_profile(p, &events))
vo->want_redraw = true;
}
if (events & VO_EVENT_RESIZE)
resize(vo);
if (events & VO_EVENT_EXPOSE)
vo->want_redraw = true;
vo_event(vo, events);
return r;
}
static void wakeup(struct vo *vo)
{
struct priv *p = vo->priv;
if (p->ra_ctx && p->ra_ctx->fns->wakeup)
p->ra_ctx->fns->wakeup(p->ra_ctx);
}
static void wait_events(struct vo *vo, int64_t until_time_us)
{
struct priv *p = vo->priv;
if (p->ra_ctx && p->ra_ctx->fns->wait_events) {
p->ra_ctx->fns->wait_events(p->ra_ctx, until_time_us);
} else {
vo_wait_default(vo, until_time_us);
}
}
static char *get_cache_file(struct priv *p)
{
struct gl_video_opts *opts = p->opts_cache->opts;
if (!opts->shader_cache_dir || !opts->shader_cache_dir[0])
return NULL;
char *dir = mp_get_user_path(NULL, p->global, opts->shader_cache_dir);
char *file = mp_path_join(NULL, dir, "libplacebo.cache");
talloc_free(dir);
return file;
}
static void uninit(struct vo *vo)
{
struct priv *p = vo->priv;
pl_queue_destroy(&p->queue); // destroy this first
for (int i = 0; i < MP_ARRAY_SIZE(p->osd_state.entries); i++)
pl_tex_destroy(p->gpu, &p->osd_state.entries[i].tex);
for (int i = 0; i < p->num_sub_tex; i++)
pl_tex_destroy(p->gpu, &p->sub_tex[i]);
for (int i = 0; i < p->num_user_hooks; i++)
pl_mpv_user_shader_destroy(&p->user_hooks[i].hook);
char *cache_file = get_cache_file(p);
if (cache_file) {
FILE *cache = fopen(cache_file, "wb");
if (cache) {
size_t size = pl_renderer_save(p->rr, NULL);
uint8_t *buf = talloc_size(NULL, size);
pl_renderer_save(p->rr, buf);
fwrite(buf, size, 1, cache);
talloc_free(buf);
fclose(cache);
}
talloc_free(cache_file);
}
pl_renderer_destroy(&p->rr);
p->ra_ctx = NULL;
p->pllog = NULL;
p->gpu = NULL;
p->sw = NULL;
gpu_ctx_destroy(&p->context);
}
static int preinit(struct vo *vo)
{
struct priv *p = vo->priv;
p->opts_cache = m_config_cache_alloc(p, vo->global, &gl_video_conf);
p->video_eq = mp_csp_equalizer_create(p, vo->global);
p->global = vo->global;
p->log = vo->log;
struct gl_video_opts *gl_opts = p->opts_cache->opts;
p->context = gpu_ctx_create(vo, gl_opts);
if (!p->context)
goto err_out;
// For the time being
p->ra_ctx = p->context->ra_ctx;
p->pllog = p->context->pllog;
p->gpu = p->context->gpu;
p->sw = p->context->swapchain;
p->rr = pl_renderer_create(p->pllog, p->gpu);
p->queue = pl_queue_create(p->gpu);
p->osd_fmt[SUBBITMAP_LIBASS] = pl_find_named_fmt(p->gpu, "r8");
p->osd_fmt[SUBBITMAP_RGBA] = pl_find_named_fmt(p->gpu, "rgba8");
char *cache_file = get_cache_file(p);
if (cache_file) {
if (stat(cache_file, &(struct stat){0}) == 0) {
bstr c = stream_read_file(cache_file, p, vo->global, 1000000000);
pl_renderer_load(p->rr, c.start);
talloc_free(c.start);
}
talloc_free(cache_file);
}
// Request as many frames as possible from the decoder. This is not really
// wasteful since we pass these through libplacebo's frame queueing
// mechanism, which only uploads frames on an as-needed basis.
vo_set_queue_params(vo, 0, VO_MAX_REQ_FRAMES);
update_render_options(p);
return 0;
err_out:
uninit(vo);
return -1;
}
static const struct pl_filter_config *map_scaler(struct priv *p,
enum scaler_unit unit)
{
static const struct pl_filter_preset fixed_scalers[] = {
{ "bilinear", &pl_filter_bilinear },
{ "bicubic_fast", &pl_filter_bicubic },
{ "nearest", &pl_filter_nearest },
{ "oversample", &pl_filter_oversample },
{0},
};
static const struct pl_filter_preset fixed_frame_mixers[] = {
{ "linear", &pl_filter_bilinear },
{ "oversample", &pl_filter_oversample },
{0},
};
const struct pl_filter_preset *fixed_presets =
unit == SCALER_TSCALE ? fixed_frame_mixers : fixed_scalers;
const struct gl_video_opts *opts = p->opts_cache->opts;
const struct scaler_config *cfg = &opts->scaler[unit];
if (unit == SCALER_DSCALE && !cfg->kernel.name)
cfg = &opts->scaler[SCALER_SCALE];
for (int i = 0; fixed_presets[i].name; i++) {
if (strcmp(cfg->kernel.name, fixed_presets[i].name) == 0)
return fixed_presets[i].filter;
}
// Attempt loading filter preset first, fall back to raw filter function
struct scaler_params *par = &p->scalers[unit];
const struct pl_filter_preset *preset;
const struct pl_filter_function_preset *fpreset;
if ((preset = pl_find_filter_preset(cfg->kernel.name))) {
par->config = *preset->filter;
par->kernel = *par->config.kernel;
} else if ((fpreset = pl_find_filter_function_preset(cfg->kernel.name))) {
par->config = (struct pl_filter_config) {0};
par->kernel = *fpreset->function;
} else if (!strcmp(cfg->kernel.name, "ewa_lanczossharp")) {
par->config = pl_filter_ewa_lanczos;
par->kernel = *par->config.kernel;
par->config.blur = 0.9812505644269356;
MP_WARN(p, "'ewa_lanczossharp' is deprecated and will be removed from "
"vo=gpu-next in the future, use --scale=ewa_lanczos "
"--scale-blur=%f to replicate it.\n", par->config.blur);
} else {
MP_ERR(p, "Failed mapping filter function '%s', no libplacebo analog?\n",
cfg->kernel.name);
return &pl_filter_bilinear;
}
par->config.kernel = &par->kernel;
if (par->config.window) {
par->window = *par->config.window;
par->config.window = &par->window;
}
const struct pl_filter_function_preset *wpreset;
if ((wpreset = pl_find_filter_function_preset(cfg->window.name)))
par->window = *wpreset->function;
for (int i = 0; i < 2; i++) {
if (!isnan(cfg->kernel.params[i]))
par->kernel.params[i] = cfg->kernel.params[i];
if (!isnan(cfg->window.params[i]))
par->window.params[i] = cfg->window.params[i];
}
par->config.clamp = cfg->clamp;
par->config.blur = cfg->kernel.blur;
par->config.taper = cfg->kernel.taper;
if (cfg->radius > 0.0) {
if (par->kernel.resizable) {
par->kernel.radius = cfg->radius;
} else {
MP_WARN(p, "Filter radius specified but filter '%s' is not "
"resizable, ignoring\n", cfg->kernel.name);
}
}
return &par->config;
}
static const struct pl_hook *load_hook(struct priv *p, const char *path)
{
if (!path || !path[0])
return NULL;
for (int i = 0; i < p->num_user_hooks; i++) {
if (strcmp(p->user_hooks[i].path, path) == 0)
return p->user_hooks[i].hook;
}
char *fname = mp_get_user_path(NULL, p->global, path);
bstr shader = stream_read_file(fname, p, p->global, 1000000000); // 1GB
talloc_free(fname);
const struct pl_hook *hook = NULL;
if (shader.len)
hook = pl_mpv_user_shader_parse(p->gpu, shader.start, shader.len);
MP_TARRAY_APPEND(p, p->user_hooks, p->num_user_hooks, (struct user_hook) {
.path = talloc_strdup(p, path),
.hook = hook,
});
return hook;
}
static void update_icc_opts(struct priv *p, const struct mp_icc_opts *opts)
{
if (!opts)
return;
#ifdef PL_HAVE_LCMS
if (!opts->profile_auto && !p->icc_path && p->icc_profile.len) {
// Un-set any auto-loaded profiles if icc-profile-auto was disabled
talloc_free((void *) p->icc_profile.data);
p->icc_profile = (struct pl_icc_profile) {0};
}
int s_r = 0, s_g = 0, s_b = 0;
gl_parse_3dlut_size(opts->size_str, &s_r, &s_g, &s_b);
p->params.icc_params = &p->icc;
p->icc = pl_icc_default_params;
p->icc.intent = opts->intent;
p->icc.size_r = s_r;
p->icc.size_g = s_g;
p->icc.size_b = s_b;
if (!opts->profile || !opts->profile[0]) {
// No profile enabled, un-load any existing profiles
if (p->icc_path) {
talloc_free((void *) p->icc_profile.data);
TA_FREEP(&p->icc_path);
p->icc_profile = (struct pl_icc_profile) {0};
}
return;
}
if (p->icc_path && strcmp(opts->profile, p->icc_path) == 0)
return; // ICC profile hasn't changed
char *fname = mp_get_user_path(NULL, p->global, opts->profile);
MP_VERBOSE(p, "Opening ICC profile '%s'\n", fname);
talloc_free((void *) p->icc_profile.data);
struct bstr icc = stream_read_file(fname, p, p->global, 100000000); // 100 MB
p->icc_profile.data = icc.start;
p->icc_profile.len = icc.len;
pl_icc_profile_compute_signature(&p->icc_profile);
talloc_free(fname);
// Update cached path
talloc_free(p->icc_path);
p->icc_path = talloc_strdup(p, opts->profile);
#endif // PL_HAVE_LCMS
}
static void update_lut(struct priv *p, struct user_lut *lut)
{
if (!lut->opt) {
pl_lut_free(&lut->lut);
TA_FREEP(&lut->path);
return;
}
if (lut->path && strcmp(lut->path, lut->opt) == 0)
return; // no change
// Update cached path
pl_lut_free(&lut->lut);
talloc_free(lut->path);
lut->path = talloc_strdup(p, lut->opt);
// Load LUT file
char *fname = mp_get_user_path(NULL, p->global, lut->path);
MP_VERBOSE(p, "Loading custom LUT '%s'\n", fname);
struct bstr lutdata = stream_read_file(fname, p, p->global, 100000000); // 100 MB
lut->lut = pl_lut_parse_cube(p->pllog, lutdata.start, lutdata.len);
talloc_free(lutdata.start);
}
static void update_render_options(struct priv *p)
{
const struct gl_video_opts *opts = p->opts_cache->opts;
p->params = pl_render_default_params;
p->params.lut_entries = 1 << opts->scaler_lut_size;
p->params.antiringing_strength = opts->scaler[0].antiring;
p->params.polar_cutoff = opts->scaler[0].cutoff;
p->params.deband_params = opts->deband ? &p->deband : NULL;
p->params.sigmoid_params = opts->sigmoid_upscaling ? &p->sigmoid : NULL;
p->params.color_adjustment = &p->color_adjustment;
p->params.peak_detect_params = opts->tone_map.compute_peak >= 0 ? &p->peak_detect : NULL;
p->params.color_map_params = &p->color_map;
p->params.background_color[0] = opts->background.r / 255.0;
p->params.background_color[1] = opts->background.g / 255.0;
p->params.background_color[2] = opts->background.b / 255.0;
p->params.skip_anti_aliasing = !opts->correct_downscaling;
p->params.disable_linear_scaling = !opts->linear_downscaling && !opts->linear_upscaling;
p->params.disable_fbos = opts->dumb_mode == 1;
p->params.blend_against_tiles = opts->alpha_mode == ALPHA_BLEND_TILES;
// Map scaler options as best we can
p->params.upscaler = map_scaler(p, SCALER_SCALE);
p->params.downscaler = map_scaler(p, SCALER_DSCALE);
p->frame_mixer = opts->interpolation ? map_scaler(p, SCALER_TSCALE) : NULL;
p->deband = pl_deband_default_params;
p->deband.iterations = opts->deband_opts->iterations;
p->deband.radius = opts->deband_opts->range;
p->deband.threshold = opts->deband_opts->threshold / 16.384;
p->deband.grain = opts->deband_opts->grain / 8.192;
p->sigmoid = pl_sigmoid_default_params;
p->sigmoid.center = opts->sigmoid_center;
p->sigmoid.slope = opts->sigmoid_slope;
p->peak_detect = pl_peak_detect_default_params;
p->peak_detect.smoothing_period = opts->tone_map.decay_rate;
p->peak_detect.scene_threshold_low = opts->tone_map.scene_threshold_low;
p->peak_detect.scene_threshold_high = opts->tone_map.scene_threshold_high;
static const enum pl_tone_mapping_algorithm tone_map_algos[] = {
[TONE_MAPPING_CLIP] = PL_TONE_MAPPING_CLIP,
[TONE_MAPPING_MOBIUS] = PL_TONE_MAPPING_MOBIUS,
[TONE_MAPPING_REINHARD] = PL_TONE_MAPPING_REINHARD,
[TONE_MAPPING_HABLE] = PL_TONE_MAPPING_HABLE,
[TONE_MAPPING_GAMMA] = PL_TONE_MAPPING_GAMMA,
[TONE_MAPPING_LINEAR] = PL_TONE_MAPPING_LINEAR,
[TONE_MAPPING_BT_2390] = PL_TONE_MAPPING_BT_2390,
};
p->color_map = pl_color_map_default_params;
p->color_map.intent = opts->icc_opts->intent;
p->color_map.tone_mapping_algo = tone_map_algos[opts->tone_map.curve];
p->color_map.tone_mapping_param = opts->tone_map.curve_param;
if (isnan(p->color_map.tone_mapping_param)) // vo_gpu compatibility
p->color_map.tone_mapping_param = 0.0;
p->color_map.desaturation_strength = opts->tone_map.desat;
p->color_map.desaturation_exponent = opts->tone_map.desat_exp;
p->color_map.max_boost = opts->tone_map.max_boost;
p->color_map.gamut_warning = opts->tone_map.gamut_warning;
p->color_map.gamut_clipping = opts->tone_map.gamut_clipping;
switch (opts->dither_algo) {
case DITHER_ERROR_DIFFUSION:
MP_ERR(p, "Error diffusion dithering is not implemented.\n");
// fall through
case DITHER_NONE:
p->params.dither_params = NULL;
break;
case DITHER_ORDERED:
case DITHER_FRUIT:
p->params.dither_params = &p->dither;
p->dither = pl_dither_default_params;
p->dither.method = opts->dither_algo == DITHER_FRUIT
? PL_DITHER_BLUE_NOISE
: PL_DITHER_ORDERED_FIXED;
p->dither.lut_size = opts->dither_size;
p->dither.temporal = opts->temporal_dither;
break;
}
if (opts->dither_depth < 0)
p->params.dither_params = NULL;
update_icc_opts(p, opts->icc_opts);
const struct pl_hook *hook;
for (int i = 0; opts->user_shaders && opts->user_shaders[i]; i++) {
if ((hook = load_hook(p, opts->user_shaders[i])))
MP_TARRAY_APPEND(p, p->hooks, p->params.num_hooks, hook);
}
p->params.hooks = p->hooks;
}
#define OPT_BASE_STRUCT struct priv
const struct m_opt_choice_alternatives lut_types[] = {
{"auto", PL_LUT_UNKNOWN},
{"native", PL_LUT_NATIVE},
{"normalized", PL_LUT_NORMALIZED},
{"conversion", PL_LUT_CONVERSION},
{0}
};
const struct vo_driver video_out_gpu_next = {
.description = "Video output based on libplacebo",
.name = "gpu-next",
.caps = VO_CAP_ROTATE90,
.preinit = preinit,
.query_format = query_format,
.reconfig = reconfig,
.control = control,
.get_image_ts = get_image,
.draw_frame = draw_frame,
.flip_page = flip_page,
.get_vsync = get_vsync,
.wait_events = wait_events,
.wakeup = wakeup,
.uninit = uninit,
.priv_size = sizeof(struct priv),
.priv_defaults = &(const struct priv) {
.delayed_peak = true,
.inter_preserve = true,
},
.options = (const struct m_option[]) {
{"allow-delayed-peak-detect", OPT_FLAG(delayed_peak)},
{"interpolation-preserve", OPT_FLAG(inter_preserve)},
{"lut", OPT_STRING(lut.opt), .flags = M_OPT_FILE},
{"lut-type", OPT_CHOICE_C(lut.type, lut_types)},
{"image-lut", OPT_STRING(image_lut.opt), .flags = M_OPT_FILE},
{"image-lut-type", OPT_CHOICE_C(image_lut.type, lut_types)},
{"target-lut", OPT_STRING(target_lut.opt), .flags = M_OPT_FILE},
{"target-colorspace-hint", OPT_FLAG(target_hint)},
// No `target-lut-type` because we don't support non-RGB targets
{0}
},
};