diff options
Diffstat (limited to 'video/out/vo_gpu_next.c')
-rw-r--r-- | video/out/vo_gpu_next.c | 1404 |
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(¶ms), + .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(¶ms), + .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, ¶ms)) { 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; } |