summaryrefslogtreecommitdiffstats
path: root/video/out/vo_gpu_next.c
diff options
context:
space:
mode:
Diffstat (limited to 'video/out/vo_gpu_next.c')
-rw-r--r--video/out/vo_gpu_next.c1404
1 files changed, 878 insertions, 526 deletions
diff --git a/video/out/vo_gpu_next.c b/video/out/vo_gpu_next.c
index e88f5c8f9f..d68c836b52 100644
--- a/video/out/vo_gpu_next.c
+++ b/video/out/vo_gpu_next.c
@@ -17,21 +17,27 @@
* License along with mpv. If not, see <http://www.gnu.org/licenses/>.
*/
-#include <pthread.h>
+#include <dirent.h>
+#include <sys/stat.h>
+#include <time.h>
+#include <unistd.h>
+
+#include <libplacebo/colorspace.h>
+#include <libplacebo/options.h>
#include <libplacebo/renderer.h>
#include <libplacebo/shaders/lut.h>
+#include <libplacebo/shaders/icc.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 "misc/io_utils.h"
#include "options/m_config.h"
+#include "options/options.h"
#include "options/path.h"
#include "osdep/io.h"
+#include "osdep/threads.h"
#include "stream/stream.h"
#include "video/fmt-conversion.h"
#include "video/mp_image.h"
@@ -55,6 +61,7 @@
#include "osdep/windows_utils.h"
#endif
+
struct osd_entry {
pl_tex tex;
struct pl_overlay_part *parts;
@@ -68,8 +75,6 @@ struct osd_state {
struct scaler_params {
struct pl_filter_config config;
- struct pl_filter_function kernel;
- struct pl_filter_function window;
};
struct user_hook {
@@ -84,6 +89,20 @@ struct user_lut {
struct pl_custom_lut *lut;
};
+struct frame_info {
+ int count;
+ struct pl_dispatch_info info[VO_PASS_PERF_MAX];
+};
+
+struct cache {
+ struct mp_log *log;
+ struct mpv_global *global;
+ char *dir;
+ const char *name;
+ size_t size_limit;
+ pl_cache cache;
+};
+
struct priv {
struct mp_log *log;
struct mpv_global *global;
@@ -93,7 +112,7 @@ struct priv {
struct ra_hwdec_mapper *hwdec_mapper;
// Allocated DR buffers
- pthread_mutex_t dr_lock;
+ mp_mutex dr_lock;
pl_buf *dr_buffers;
int num_dr_buffers;
@@ -115,72 +134,111 @@ struct priv {
double last_pts;
bool is_interpolated;
bool want_reset;
+ bool frame_pending;
+ bool redraw;
+ pl_options pars;
struct m_config_cache *opts_cache;
+ struct m_config_cache *next_opts_cache;
+ struct gl_next_opts *next_opts;
+ struct cache shader_cache, icc_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;
- enum mp_csp_levels output_levels;
+ enum pl_color_levels output_levels;
-#ifdef PL_HAVE_LCMS
- struct pl_icc_params icc;
- struct pl_icc_profile icc_profile;
+ struct pl_icc_params icc_params;
char *icc_path;
-#endif
-
- struct user_lut image_lut;
- struct user_lut target_lut;
- struct user_lut lut;
+ pl_icc_object icc_profile;
// 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;
+ struct frame_info perf_fresh;
+ struct frame_info perf_redraw;
- int delayed_peak;
- int inter_preserve;
- int target_hint;
+ struct mp_image_params target_params;
};
static void update_render_options(struct vo *vo);
static void update_lut(struct priv *p, struct user_lut *lut);
+struct gl_next_opts {
+ bool delayed_peak;
+ int border_background;
+ float corner_rounding;
+ bool inter_preserve;
+ struct user_lut lut;
+ struct user_lut image_lut;
+ struct user_lut target_lut;
+ bool target_hint;
+ char **raw_opts;
+};
+
+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}
+};
+
+#define OPT_BASE_STRUCT struct gl_next_opts
+const struct m_sub_options gl_next_conf = {
+ .opts = (const struct m_option[]) {
+ {"allow-delayed-peak-detect", OPT_BOOL(delayed_peak)},
+ {"border-background", OPT_CHOICE(border_background,
+ {"none", BACKGROUND_NONE},
+ {"color", BACKGROUND_COLOR},
+ {"tiles", BACKGROUND_TILES})},
+ {"corner-rounding", OPT_FLOAT(corner_rounding), M_RANGE(0, 1)},
+ {"interpolation-preserve", OPT_BOOL(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_BOOL(target_hint)},
+ // No `target-lut-type` because we don't support non-RGB targets
+ {"libplacebo-opts", OPT_KEYVALUELIST(raw_opts)},
+ {0},
+ },
+ .defaults = &(struct gl_next_opts) {
+ .border_background = BACKGROUND_COLOR,
+ .inter_preserve = true,
+ },
+ .size = sizeof(struct gl_next_opts),
+ .change_flags = UPDATE_VIDEO,
+};
+
static pl_buf get_dr_buf(struct priv *p, const uint8_t *ptr)
{
- pthread_mutex_lock(&p->dr_lock);
+ mp_mutex_lock(&p->dr_lock);
for (int i = 0; i < p->num_dr_buffers; i++) {
pl_buf buf = p->dr_buffers[i];
if (ptr >= buf->data && ptr < buf->data + buf->params.size) {
- pthread_mutex_unlock(&p->dr_lock);
+ mp_mutex_unlock(&p->dr_lock);
return buf;
}
}
- pthread_mutex_unlock(&p->dr_lock);
+ mp_mutex_unlock(&p->dr_lock);
return NULL;
}
static void free_dr_buf(void *opaque, uint8_t *data)
{
struct priv *p = opaque;
- pthread_mutex_lock(&p->dr_lock);
+ mp_mutex_lock(&p->dr_lock);
for (int i = 0; i < p->num_dr_buffers; i++) {
if (p->dr_buffers[i]->data == data) {
pl_buf_destroy(p->gpu, &p->dr_buffers[i]);
MP_TARRAY_REMOVE_AT(p->dr_buffers, p->num_dr_buffers, i);
- pthread_mutex_unlock(&p->dr_lock);
+ mp_mutex_unlock(&p->dr_lock);
return;
}
}
@@ -189,13 +247,18 @@ static void free_dr_buf(void *opaque, uint8_t *data)
}
static struct mp_image *get_image(struct vo *vo, int imgfmt, int w, int h,
- int stride_align)
+ int stride_align, int flags)
{
struct priv *p = vo->priv;
pl_gpu gpu = p->gpu;
if (!gpu->limits.thread_safe || !gpu->limits.max_mapped_size)
return NULL;
+ if ((flags & VO_DR_FLAG_HOST_CACHED) && !gpu->limits.host_cached)
+ return NULL;
+
+ stride_align = mp_lcm(stride_align, gpu->limits.align_tex_xfer_pitch);
+ stride_align = mp_lcm(stride_align, gpu->limits.align_tex_xfer_offset);
int size = mp_image_get_alloc_size(imgfmt, w, h, stride_align);
if (size < 0)
return NULL;
@@ -217,16 +280,17 @@ static struct mp_image *get_image(struct vo *vo, int imgfmt, int w, int h,
return NULL;
}
- pthread_mutex_lock(&p->dr_lock);
+ mp_mutex_lock(&p->dr_lock);
MP_TARRAY_APPEND(p, p->dr_buffers, p->num_dr_buffers, buf);
- pthread_mutex_unlock(&p->dr_lock);
+ mp_mutex_unlock(&p->dr_lock);
return mpi;
}
-static void update_overlays(struct vo *vo, struct mp_osd_res res, double pts,
+static void update_overlays(struct vo *vo, struct mp_osd_res res,
int flags, enum pl_overlay_coords coords,
- struct osd_state *state, struct pl_frame *frame)
+ struct osd_state *state, struct pl_frame *frame,
+ struct mp_image *src)
{
struct priv *p = vo->priv;
static const bool subfmt_all[SUBBITMAP_COUNT] = {
@@ -234,7 +298,9 @@ static void update_overlays(struct vo *vo, struct mp_osd_res res, double pts,
[SUBBITMAP_BGRA] = true,
};
+ double pts = src ? src->pts : 0;
struct sub_bitmap_list *subs = osd_render(vo->osd, res, pts, flags, subfmt_all);
+
frame->overlays = state->overlays;
frame->num_overlays = 0;
@@ -260,7 +326,7 @@ static void update_overlays(struct vo *vo, struct mp_osd_res res, double pts,
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,
+ .row_pitch = item->packed->stride[0],
.ptr = item->packed->planes[0],
});
if (!ok) {
@@ -271,6 +337,8 @@ static void update_overlays(struct vo *vo, struct mp_osd_res res, double pts,
entry->num_parts = 0;
for (int i = 0; i < item->num_parts; i++) {
const struct sub_bitmap *b = &item->parts[i];
+ if (b->dw == 0 || b->dh == 0)
+ continue;
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 },
@@ -290,24 +358,31 @@ static void update_overlays(struct vo *vo, struct mp_osd_res res, double pts,
.tex = entry->tex,
.parts = entry->parts,
.num_parts = entry->num_parts,
- .color.primaries = frame->color.primaries,
- .color.transfer = frame->color.transfer,
+ .color = {
+ .primaries = PL_COLOR_PRIM_BT_709,
+ .transfer = PL_COLOR_TRC_SRGB,
+ },
.coords = coords,
};
- // Reject HDR/wide gamut subtitles out of the box, since these are
- // probably not intended to match the video color space.
- if (pl_color_primaries_is_wide_gamut(ol->color.primaries))
- ol->color.primaries = PL_COLOR_PRIM_UNKNOWN;
- if (pl_color_transfer_is_hdr(ol->color.transfer))
- ol->color.transfer = PL_COLOR_TRC_UNKNOWN;
-
switch (item->format) {
case SUBBITMAP_BGRA:
ol->mode = PL_OVERLAY_NORMAL;
ol->repr.alpha = PL_ALPHA_PREMULTIPLIED;
+ // Infer bitmap colorspace from source
+ if (src) {
+ ol->color = src->params.color;
+ // Seems like HDR subtitles are targeting SDR white
+ if (pl_color_transfer_is_hdr(ol->color.transfer)) {
+ ol->color.hdr = (struct pl_hdr_metadata) {
+ .max_luma = PL_COLOR_SDR_WHITE,
+ };
+ }
+ }
break;
case SUBBITMAP_LIBASS:
+ if (src && item->video_color_space && !pl_color_space_is_hdr(&src->params.color))
+ ol->color = src->params.color;
ol->mode = PL_OVERLAY_MONOCHROME;
ol->repr.alpha = PL_ALPHA_INDEPENDENT;
break;
@@ -417,54 +492,15 @@ static int plane_data_from_imgfmt(struct pl_plane_data out_data[4],
return desc.num_planes;
}
-static struct pl_color_space get_mpi_csp(struct vo *vo, struct mp_image *mpi)
-{
- struct pl_color_space csp = {
- .primaries = mp_prim_to_pl(mpi->params.color.primaries),
- .transfer = mp_trc_to_pl(mpi->params.color.gamma),
- .hdr.max_luma = mpi->params.color.sig_peak * MP_REF_WHITE,
- };
-
- 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;
- csp.hdr.max_cll = clm->MaxCLL;
- csp.hdr.max_fall = clm->MaxFALL;
- break;
- }
- case AV_FRAME_DATA_MASTERING_DISPLAY_METADATA: {
- const AVMasteringDisplayMetadata *mdm = data;
- if (mdm->has_luminance) {
- csp.hdr.min_luma = av_q2d(mdm->min_luminance);
- csp.hdr.max_luma = av_q2d(mdm->max_luminance);
- }
-
- if (mdm->has_primaries) {
- csp.hdr.prim.red.x = av_q2d(mdm->display_primaries[0][0]);
- csp.hdr.prim.red.y = av_q2d(mdm->display_primaries[0][1]);
- csp.hdr.prim.green.x = av_q2d(mdm->display_primaries[1][0]);
- csp.hdr.prim.green.y = av_q2d(mdm->display_primaries[1][1]);
- csp.hdr.prim.blue.x = av_q2d(mdm->display_primaries[2][0]);
- csp.hdr.prim.blue.y = av_q2d(mdm->display_primaries[2][1]);
- csp.hdr.prim.white.x = av_q2d(mdm->white_point[0]);
- csp.hdr.prim.white.y = av_q2d(mdm->white_point[1]);
- }
- break;
- }
- default: break;
- }
- }
-
- return csp;
-}
-
static bool hwdec_reconfig(struct priv *p, struct ra_hwdec *hwdec,
const struct mp_image_params *par)
{
if (p->hwdec_mapper) {
- if (mp_image_params_equal(par, &p->hwdec_mapper->src_params)) {
+ if (mp_image_params_static_equal(par, &p->hwdec_mapper->src_params)) {
+ p->hwdec_mapper->src_params.repr.dovi = par->repr.dovi;
+ p->hwdec_mapper->dst_params.repr.dovi = par->repr.dovi;
+ p->hwdec_mapper->src_params.color.hdr = par->color.hdr;
+ p->hwdec_mapper->dst_params.color.hdr = par->color.hdr;
return p->hwdec_mapper;
} else {
ra_hwdec_mapper_free(&p->hwdec_mapper);
@@ -580,12 +616,8 @@ static bool map_frame(pl_gpu gpu, pl_tex *tex, const struct pl_source_frame *src
}
*frame = (struct pl_frame) {
- .color = get_mpi_csp(vo, mpi),
- .repr = {
- .sys = mp_csp_to_pl(par->color.space),
- .levels = mp_levels_to_pl(par->color.levels),
- .alpha = mp_alpha_to_pl(par->alpha),
- },
+ .color = par->color,
+ .repr = par->repr,
.profile = {
.data = mpi->icc_profile ? mpi->icc_profile->data : NULL,
.len = mpi->icc_profile ? mpi->icc_profile->size : 0,
@@ -597,14 +629,14 @@ static bool map_frame(pl_gpu gpu, pl_tex *tex, const struct pl_source_frame *src
// 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:
+ case PL_COLOR_SYSTEM_RGB:
frame->repr.sys = PL_COLOR_SYSTEM_RGB;
frame->repr.levels = PL_COLOR_LEVELS_FULL;
break;
- case MP_CSP_XYZ:
+ case PL_COLOR_SYSTEM_XYZ:
frame->repr.sys = PL_COLOR_SYSTEM_XYZ;
break;
- case MP_CSP_AUTO:
+ case PL_COLOR_SYSTEM_UNKNOWN:
if (!frame->repr.sys)
frame->repr.sys = pl_color_system_guess_ycbcr(par->w, par->h);
break;
@@ -673,29 +705,10 @@ static bool map_frame(pl_gpu gpu, pl_tex *tex, const struct pl_source_frame *src
}
// Update chroma location, must be done after initializing planes
- pl_frame_set_chroma_location(frame, mp_chroma_to_pl(par->chroma_location));
-
-#ifdef PL_HAVE_LAV_DOLBY_VISION
- if (mpi->dovi) {
- const AVDOVIMetadata *metadata = (AVDOVIMetadata *) mpi->dovi->data;
- struct pl_dovi_metadata *dovi = talloc_ptrtype(mpi, dovi);
- const AVDOVIColorMetadata *color = av_dovi_get_color(metadata);
- pl_map_dovi_metadata(dovi, metadata);
- frame->repr.dovi = dovi;
- frame->repr.sys = PL_COLOR_SYSTEM_DOLBYVISION;
- frame->color.primaries = PL_COLOR_PRIM_BT_2020;
- frame->color.transfer = PL_COLOR_TRC_PQ;
- frame->color.hdr.min_luma =
- pl_hdr_rescale(PL_HDR_PQ, PL_HDR_NITS, color->source_min_pq / 4095.0f);
- frame->color.hdr.max_luma =
- pl_hdr_rescale(PL_HDR_PQ, PL_HDR_NITS, color->source_max_pq / 4095.0f);
- }
-#endif
+ pl_frame_set_chroma_location(frame, par->chroma_location);
-#ifdef PL_HAVE_LAV_FILM_GRAIN
if (mpi->film_grain)
pl_film_grain_from_av(&frame->film_grain, (AVFilmGrainParams *) mpi->film_grain->data);
-#endif
// Compute a unique signature for any attached ICC profile. Wasteful in
// theory if the ICC profile is the same for multiple frames, but in
@@ -704,9 +717,9 @@ static bool map_frame(pl_gpu gpu, pl_tex *tex, const struct pl_source_frame *src
pl_icc_profile_compute_signature(&frame->profile);
// 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;
+ update_lut(p, &p->next_opts->image_lut);
+ frame->lut = p->next_opts->image_lut.lut;
+ frame->lut_type = p->next_opts->image_lut.type;
return true;
}
@@ -734,88 +747,127 @@ static void info_callback(void *priv, const struct pl_render_info *info)
{
struct vo *vo = priv;
struct priv *p = vo->priv;
- if (info->index > VO_PASS_PERF_MAX)
+ if (info->index >= VO_PASS_PERF_MAX)
return; // silently ignore clipped passes, whatever
- struct mp_frame_perf *frame;
+ struct frame_info *frame;
switch (info->stage) {
- case PL_RENDER_STAGE_FRAME: frame = &p->perf.fresh; break;
- case PL_RENDER_STAGE_BLEND: frame = &p->perf.redraw; break;
+ case PL_RENDER_STAGE_FRAME: frame = &p->perf_fresh; break;
+ case PL_RENDER_STAGE_BLEND: frame = &p->perf_redraw; break;
default: abort();
}
- int index = info->index;
-#if PL_API_VER < 227
- // Versions of libplacebo older than this used `index` to communicate the
- // blended frame count, and implicitly clipped all subsequent passes. This
- // functionaliy was removed in API ver 227, which makes `index` behave the
- // same for frame and blend stages.
- if (info->stage == PL_RENDER_STAGE_BLEND)
- index = 0;
-#endif
-
- struct mp_pass_perf *perf = &frame->perf[index];
- const struct pl_dispatch_info *pass = info->pass;
- static_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;
+ frame->count = info->index + 1;
+ pl_dispatch_info_move(&frame->info[info->index], info->pass);
}
static void update_options(struct vo *vo)
{
struct priv *p = vo->priv;
- if (m_config_cache_update(p->opts_cache))
+ pl_options pars = p->pars;
+ bool changed = m_config_cache_update(p->opts_cache);
+ changed = m_config_cache_update(p->next_opts_cache) || changed;
+ if (changed)
update_render_options(vo);
- update_lut(p, &p->lut);
- p->params.lut = p->lut.lut;
- p->params.lut_type = p->lut.type;
+ update_lut(p, &p->next_opts->lut);
+ pars->params.lut = p->next_opts->lut.lut;
+ pars->params.lut_type = p->next_opts->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;
+ pars->color_adjustment.brightness = cparams.brightness;
+ pars->color_adjustment.contrast = cparams.contrast;
+ pars->color_adjustment.hue = cparams.hue;
+ pars->color_adjustment.saturation = cparams.saturation;
+ pars->color_adjustment.gamma = cparams.gamma;
p->output_levels = cparams.levels_out;
+
+ for (char **kv = p->next_opts->raw_opts; kv && kv[0]; kv += 2)
+ pl_options_set_str(pars, kv[0], kv[1]);
}
-static void apply_target_options(struct priv *p, struct pl_frame *target)
+static void apply_target_contrast(struct priv *p, struct pl_color_space *color)
{
+ const struct gl_video_opts *opts = p->opts_cache->opts;
- update_lut(p, &p->target_lut);
- target->lut = p->target_lut.lut;
- target->lut_type = p->target_lut.type;
+ // Auto mode, leave as is
+ if (!opts->target_contrast)
+ return;
-#ifdef PL_HAVE_LCMS
- target->profile = p->icc_profile;
-#endif
+ // Infinite contrast
+ if (opts->target_contrast == -1) {
+ color->hdr.min_luma = 1e-7;
+ return;
+ }
+
+ // Infer max_luma for current pl_color_space
+ pl_color_space_nominal_luma_ex(pl_nominal_luma_params(
+ .color = color,
+ // with HDR10 meta to respect value if already set
+ .metadata = PL_HDR_METADATA_HDR10,
+ .scaling = PL_HDR_NITS,
+ .out_max = &color->hdr.max_luma
+ ));
+
+ color->hdr.min_luma = color->hdr.max_luma / opts->target_contrast;
+}
+
+static void apply_target_options(struct priv *p, struct pl_frame *target)
+{
+ update_lut(p, &p->next_opts->target_lut);
+ target->lut = p->next_opts->target_lut.lut;
+ target->lut_type = p->next_opts->target_lut.type;
// Colorspace overrides
const struct gl_video_opts *opts = p->opts_cache->opts;
if (p->output_levels)
- target->repr.levels = mp_levels_to_pl(p->output_levels);
+ target->repr.levels = p->output_levels;
if (opts->target_prim)
- target->color.primaries = mp_prim_to_pl(opts->target_prim);
+ target->color.primaries = opts->target_prim;
if (opts->target_trc)
- target->color.transfer = mp_trc_to_pl(opts->target_trc);
- if (opts->target_peak)
+ target->color.transfer = opts->target_trc;
+ // If swapchain returned a value use this, override is used in hint
+ if (opts->target_peak && !target->color.hdr.max_luma)
target->color.hdr.max_luma = opts->target_peak;
- if (opts->dither_depth > 0) {
+ if (!target->color.hdr.min_luma)
+ apply_target_contrast(p, &target->color);
+ if (opts->target_gamut) {
+ // Ensure resulting gamut still fits inside container
+ const struct pl_raw_primaries *gamut, *container;
+ gamut = pl_raw_primaries_get(opts->target_gamut);
+ container = pl_raw_primaries_get(target->color.primaries);
+ target->color.hdr.prim = pl_primaries_clip(gamut, container);
+ }
+ int dither_depth = opts->dither_depth;
+ if (dither_depth == 0) {
+ struct ra_swapchain *sw = p->ra_ctx->swapchain;
+ if (sw->fns->color_depth) {
+ dither_depth = sw->fns->color_depth(sw);
+ } else if (!pl_color_transfer_is_hdr(target->color.transfer)) {
+ dither_depth = 8;
+ }
+ }
+ if (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;
+ tbits->color_depth += dither_depth - tbits->sample_depth;
+ tbits->sample_depth = dither_depth;
}
+
+ if (opts->icc_opts->icc_use_luma) {
+ p->icc_params.max_luma = 0.0f;
+ } else {
+ pl_color_space_nominal_luma_ex(pl_nominal_luma_params(
+ .color = &target->color,
+ .metadata = PL_HDR_METADATA_HDR10, // use only static HDR nits
+ .scaling = PL_HDR_NITS,
+ .out_max = &p->icc_params.max_luma,
+ ));
+ }
+
+ pl_icc_update(p->pllog, &p->icc_profile, NULL, &p->icc_params);
+ target->icc = p->icc_profile;
}
static void apply_crop(struct pl_frame *frame, struct mp_rect crop,
@@ -841,27 +893,84 @@ static void apply_crop(struct pl_frame *frame, struct mp_rect crop,
}
}
+static void update_tm_viz(struct pl_color_map_params *params,
+ const struct pl_frame *target)
+{
+ if (!params->visualize_lut)
+ return;
+
+ // Use right half of sceen for TM visualization, constrain to 1:1 AR
+ const float out_w = fabsf(pl_rect_w(target->crop));
+ const float out_h = fabsf(pl_rect_h(target->crop));
+ const float size = MPMIN(out_w / 2.0f, out_h);
+ params->visualize_rect = (pl_rect2df) {
+ .x0 = 1.0f - size / out_w,
+ .x1 = 1.0f,
+ .y0 = 0.0f,
+ .y1 = size / out_h,
+ };
+
+ // Visualize red-blue plane
+ params->visualize_hue = M_PI / 4.0;
+}
+
static void draw_frame(struct vo *vo, struct vo_frame *frame)
{
struct priv *p = vo->priv;
+ pl_options pars = p->pars;
pl_gpu gpu = p->gpu;
update_options(vo);
- p->params.info_callback = info_callback;
- p->params.info_priv = vo;
+
+ struct pl_render_params params = pars->params;
+ const struct gl_video_opts *opts = p->opts_cache->opts;
+ bool will_redraw = frame->display_synced && frame->num_vsyncs > 1;
+ bool cache_frame = will_redraw || frame->still;
+ bool can_interpolate = opts->interpolation && frame->display_synced &&
+ !frame->still && frame->num_frames > 1;
+ double pts_offset = can_interpolate ? frame->ideal_frame_vsync : 0;
+ params.info_callback = info_callback;
+ params.info_priv = vo;
+ params.skip_caching_single_frame = !cache_frame;
+ params.preserve_mixing_cache = p->next_opts->inter_preserve && !frame->still;
+ if (frame->still)
+ params.frame_mixer = NULL;
+
+ // pl_queue advances its internal virtual PTS and culls available frames
+ // based on this value and the VPS/FPS ratio. Requesting a non-monotonic PTS
+ // is an invalid use of pl_queue. Reset it if this happens in an attempt to
+ // recover as much as possible. Ideally, this should never occur, and if it
+ // does, it should be corrected. The ideal_frame_vsync may be negative if
+ // the last draw did not align perfectly with the vsync. In this case, we
+ // should have the previous frame available in pl_queue, or a reset is
+ // already requested. Clamp the check to 0, as we don't have the previous
+ // frame in vo_frame anyway.
+ struct pl_source_frame vpts;
+ if (frame->current && !p->want_reset) {
+ if (pl_queue_peek(p->queue, 0, &vpts) &&
+ frame->current->pts + MPMAX(0, pts_offset) < vpts.pts)
+ {
+ MP_VERBOSE(vo, "Forcing queue refill, PTS(%f + %f | %f) < VPTS(%f)\n",
+ frame->current->pts, pts_offset,
+ frame->ideal_frame_vsync_duration, vpts.pts);
+ p->want_reset = true;
+ }
+ }
// 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->last_id = 0;
p->want_reset = false;
}
+ if (id <= p->last_id)
+ continue; // ignore already seen frames
+
struct mp_image *mpi = mp_image_new_ref(frame->frames[n]);
struct frame_priv *fp = talloc_zero(mpi, struct frame_priv);
mpi->priv = fp;
@@ -869,6 +978,7 @@ static void draw_frame(struct vo *vo, struct vo_frame *frame)
pl_queue_push(p->queue, &(struct pl_source_frame) {
.pts = mpi->pts,
+ .duration = can_interpolate ? frame->approx_duration : 0,
.frame_data = mpi,
.map = map_frame,
.unmap = unmap_frame,
@@ -878,29 +988,35 @@ static void draw_frame(struct vo *vo, struct vo_frame *frame)
p->last_id = id;
}
- const struct gl_video_opts *opts = p->opts_cache->opts;
- if (p->target_hint && frame->current) {
- struct pl_color_space hint = get_mpi_csp(vo, frame->current);
+ if (p->next_opts->target_hint && frame->current) {
+ struct pl_color_space hint = frame->current->params.color;
if (opts->target_prim)
- hint.primaries = mp_prim_to_pl(opts->target_prim);
+ hint.primaries = opts->target_prim;
if (opts->target_trc)
- hint.transfer = mp_trc_to_pl(opts->target_trc);
+ hint.transfer = opts->target_trc;
+ if (opts->target_peak)
+ hint.hdr.max_luma = opts->target_peak;
+ apply_target_contrast(p, &hint);
pl_swapchain_colorspace_hint(p->sw, &hint);
- } else if (!p->target_hint) {
+ } else if (!p->next_opts->target_hint) {
pl_swapchain_colorspace_hint(p->sw, NULL);
}
struct pl_swapchain_frame swframe;
struct ra_swapchain *sw = p->ra_ctx->swapchain;
- double vsync_offset = opts->interpolation ? frame->vsync_offset : 0;
bool should_draw = sw->fns->start_frame(sw, NULL); // for wayland logic
if (!should_draw || !pl_swapchain_start_frame(p->sw, &swframe)) {
if (frame->current) {
// 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),
- ));
+ struct pl_queue_params qparams = *pl_queue_params(
+ .pts = frame->current->pts + pts_offset,
+ .radius = pl_frame_mix_radius(&params),
+ .vsync_duration = can_interpolate ? frame->ideal_frame_vsync_duration : 0,
+ );
+#if PL_API_VER >= 340
+ qparams.drift_compensation = 0;
+#endif
+ pl_queue_update(p->queue, NULL, &qparams);
}
return;
}
@@ -912,26 +1028,37 @@ static void draw_frame(struct vo *vo, struct vo_frame *frame)
struct pl_frame target;
pl_frame_from_swapchain(&target, &swframe);
apply_target_options(p, &target);
- update_overlays(vo, p->osd_res, frame->current ? frame->current->pts : 0,
+ update_overlays(vo, p->osd_res,
(frame->current && opts->blend_subs) ? OSD_DRAW_OSD_ONLY : 0,
- PL_OVERLAY_COORDS_DST_FRAME, &p->osd_state, &target);
+ PL_OVERLAY_COORDS_DST_FRAME, &p->osd_state, &target, frame->current);
apply_crop(&target, p->dst, swframe.fbo->params.w, swframe.fbo->params.h);
+ update_tm_viz(&pars->color_map_params, &target);
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,
+ struct pl_queue_params qparams = *pl_queue_params(
+ .pts = frame->current->pts + pts_offset,
+ .radius = pl_frame_mix_radius(&params),
+ .vsync_duration = can_interpolate ? frame->ideal_frame_vsync_duration : 0,
.interpolation_threshold = opts->interpolation_threshold,
- };
+ );
+#if PL_API_VER >= 340
+ qparams.drift_compensation = 0;
+#endif
- // 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);
+ // Depending on the vsync ratio, we may be up to half of the vsync
+ // duration before the current frame time. This works fine because
+ // pl_queue will have this frame, unless it's after a reset event. In
+ // this case, start from the first available frame.
+ struct pl_source_frame first;
+ if (pl_queue_peek(p->queue, 0, &first) && qparams.pts < first.pts) {
+ if (first.pts != frame->current->pts)
+ MP_VERBOSE(vo, "Current PTS(%f) != VPTS(%f)\n", frame->current->pts, first.pts);
+ MP_VERBOSE(vo, "Clamping first frame PTS from %f to %f\n", qparams.pts, first.pts);
+ qparams.pts = first.pts;
+ }
+ p->last_pts = qparams.pts;
switch (pl_queue_update(p->queue, &mix, &qparams)) {
case PL_QUEUE_ERR:
@@ -957,10 +1084,10 @@ static void draw_frame(struct vo *vo, struct vo_frame *frame)
struct mp_image *mpi = image->user_data;
struct frame_priv *fp = mpi->priv;
apply_crop(image, p->src, vo->params->w, vo->params->h);
-
if (opts->blend_subs) {
+ if (frame->redraw)
+ p->osd_sync++;
if (fp->osd_sync < p->osd_sync) {
- // Only update the overlays if the state has changed
float rx = pl_rect_w(p->dst) / pl_rect_w(image->crop);
float ry = pl_rect_h(p->dst) / pl_rect_h(image->crop);
struct mp_osd_res res = {
@@ -972,9 +1099,9 @@ static void draw_frame(struct vo *vo, struct vo_frame *frame)
.mb = (image->crop.y1 - vo->params->h) * ry,
.display_par = 1.0,
};
- update_overlays(vo, res, mpi->pts, OSD_DRAW_SUB_ONLY,
+ update_overlays(vo, res, OSD_DRAW_SUB_ONLY,
PL_OVERLAY_COORDS_DST_CROP,
- &fp->subs, image);
+ &fp->subs, image, mpi);
fp->osd_sync = p->osd_sync;
}
} else {
@@ -994,22 +1121,35 @@ static void draw_frame(struct vo *vo, struct vo_frame *frame)
}
}
-#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)) {
+ if (!pl_render_image_mix(p->rr, &mix, &target, &params)) {
MP_ERR(vo, "Failed rendering frame!\n");
goto done;
}
- p->is_interpolated = mix.num_frames > 1;
+ struct pl_frame ref_frame;
+ pl_frames_infer_mix(p->rr, &mix, &target, &ref_frame);
+
+ mp_mutex_lock(&vo->params_mutex);
+ p->target_params = (struct mp_image_params){
+ .imgfmt_name = swframe.fbo->params.format
+ ? swframe.fbo->params.format->name : NULL,
+ .w = mp_rect_w(p->dst),
+ .h = mp_rect_h(p->dst),
+ .color = target.color,
+ .repr = target.repr,
+ .rotate = target.rotation,
+ };
+ vo->target_params = &p->target_params;
+
+ if (vo->params) {
+ vo->params->color.hdr = ref_frame.color.hdr;
+ // Augment metadata with peak detection max_pq_y / avg_pq_y
+ pl_renderer_get_hdr_metadata(p->rr, &vo->params->color.hdr);
+ }
+ mp_mutex_unlock(&vo->params_mutex);
+
+ p->is_interpolated = pts_offset != 0 && mix.num_frames > 1;
valid = true;
// fall through
@@ -1017,14 +1157,21 @@ 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");
+ pl_gpu_flush(gpu);
+ p->frame_pending = true;
}