/* * This file is part of mpv. * * mpv is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 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 General Public License for more details. * * You should have received a copy of the GNU General Public License along * with mpv. If not, see . */ #include #include "vdpau.h" #include "osdep/timer.h" #include "video/out/x11_common.h" #include "video/img_format.h" #include "video/mp_image.h" static void mark_vdpau_objects_uninitialized(struct mp_vdpau_ctx *ctx) { for (int i = 0; i < MAX_VIDEO_SURFACES; i++) ctx->video_surfaces[i].surface = VDP_INVALID_HANDLE; ctx->vdp_device = VDP_INVALID_HANDLE; } static void preemption_callback(VdpDevice device, void *context) { struct mp_vdpau_ctx *ctx = context; ctx->is_preempted = true; } static int win_x11_init_vdpau_procs(struct mp_vdpau_ctx *ctx) { struct vo_x11_state *x11 = ctx->x11; VdpStatus vdp_st; // Don't operate on ctx->vdp directly, so that even if init fails, ctx->vdp // will have the function pointers from the previous successful init, and // won't randomly make other code crash on calling NULL pointers. struct vdp_functions vdp = {0}; if (!x11) return -1; struct vdp_function { const int id; int offset; }; static const struct vdp_function vdp_func[] = { #define VDP_FUNCTION(_, macro_name, mp_name) {macro_name, offsetof(struct vdp_functions, mp_name)}, #include "video/vdpau_functions.inc" #undef VDP_FUNCTION {0, -1} }; VdpGetProcAddress *get_proc_address; vdp_st = vdp_device_create_x11(x11->display, x11->screen, &ctx->vdp_device, &get_proc_address); if (vdp_st != VDP_STATUS_OK) { if (ctx->is_preempted) MP_DBG(ctx, "Error calling vdp_device_create_x11 while preempted: %d\n", vdp_st); else MP_ERR(ctx, "Error when calling vdp_device_create_x11: %d\n", vdp_st); return -1; } for (const struct vdp_function *dsc = vdp_func; dsc->offset >= 0; dsc++) { vdp_st = get_proc_address(ctx->vdp_device, dsc->id, (void **)((char *)&vdp + dsc->offset)); if (vdp_st != VDP_STATUS_OK) { MP_ERR(ctx, "Error when calling vdp_get_proc_address(function " "id %d): %s\n", dsc->id, vdp.get_error_string ? vdp.get_error_string(vdp_st) : "?"); return -1; } } *ctx->vdp = vdp; ctx->get_proc_address = get_proc_address; vdp_st = vdp.preemption_callback_register(ctx->vdp_device, preemption_callback, ctx); return 0; } static int handle_preemption(struct mp_vdpau_ctx *ctx) { if (!ctx->is_preempted) return 0; mark_vdpau_objects_uninitialized(ctx); if (!ctx->preemption_user_notified) { MP_ERR(ctx, "Got display preemption notice! Will attempt to recover.\n"); ctx->preemption_user_notified = true; } /* Trying to initialize seems to be quite slow, so only try once a * second to avoid using 100% CPU. */ if (ctx->last_preemption_retry_fail && mp_time_sec() - ctx->last_preemption_retry_fail < 1.0) return -1; if (win_x11_init_vdpau_procs(ctx) < 0) { ctx->last_preemption_retry_fail = mp_time_sec(); return -1; } ctx->preemption_user_notified = false; ctx->last_preemption_retry_fail = 0; ctx->is_preempted = false; ctx->preemption_counter++; MP_INFO(ctx, "Recovered from display preemption.\n"); return 1; } // Check whether vdpau initialization and preemption status is ok and we can // proceed normally. bool mp_vdpau_status_ok(struct mp_vdpau_ctx *ctx) { return handle_preemption(ctx) >= 0; } static void release_decoder_surface(void *ptr) { bool *in_use_ptr = ptr; *in_use_ptr = false; } static struct mp_image *create_ref(struct surface_entry *e) { assert(!e->in_use); e->in_use = true; struct mp_image *res = mp_image_new_custom_ref(&(struct mp_image){0}, &e->in_use, release_decoder_surface); mp_image_setfmt(res, e->fmt); mp_image_set_size(res, e->w, e->h); res->planes[0] = (void *)"dummy"; // must be non-NULL, otherwise arbitrary res->planes[3] = (void *)(intptr_t)e->surface; return res; } struct mp_image *mp_vdpau_get_video_surface(struct mp_vdpau_ctx *ctx, int fmt, VdpChromaType chroma, int w, int h) { struct vdp_functions *vdp = ctx->vdp; VdpStatus vdp_st; assert(IMGFMT_IS_VDPAU(fmt)); // Destroy all unused surfaces that don't have matching parameters for (int n = 0; n < MAX_VIDEO_SURFACES; n++) { struct surface_entry *e = &ctx->video_surfaces[n]; if (!e->in_use && e->surface != VDP_INVALID_HANDLE) { if (e->fmt != fmt || e->chroma != chroma || e->w != w || e->h != h) { vdp_st = vdp->video_surface_destroy(e->surface); CHECK_ST_WARNING("Error when calling vdp_video_surface_destroy"); e->surface = VDP_INVALID_HANDLE; } } } // Try to find an existing unused surface for (int n = 0; n < MAX_VIDEO_SURFACES; n++) { struct surface_entry *e = &ctx->video_surfaces[n]; if (!e->in_use && e->surface != VDP_INVALID_HANDLE) { assert(e->w == w && e->h == h); assert(e->fmt == fmt && e->chroma == chroma); return create_ref(e); } } // Allocate new surface for (int n = 0; n < MAX_VIDEO_SURFACES; n++) { struct surface_entry *e = &ctx->video_surfaces[n]; if (!e->in_use) { assert(e->surface == VDP_INVALID_HANDLE); e->fmt = fmt; e->chroma = chroma; e->w = w; e->h = h; if (ctx->is_preempted) { MP_WARN(ctx, "Preempted, no surface.\n"); } else { vdp_st = vdp->video_surface_create(ctx->vdp_device, chroma, w, h, &e->surface); CHECK_ST_WARNING("Error when calling vdp_video_surface_create"); } return create_ref(e); } } MP_ERR(ctx, "no surfaces available in mp_vdpau_get_video_surface\n"); return NULL; } struct mp_vdpau_ctx *mp_vdpau_create_device_x11(struct mp_log *log, struct vo_x11_state *x11) { struct mp_vdpau_ctx *ctx = talloc_ptrtype(NULL, ctx); *ctx = (struct mp_vdpau_ctx) { .log = log, .x11 = x11, .vdp = talloc_zero(ctx, struct vdp_functions), }; mark_vdpau_objects_uninitialized(ctx); if (win_x11_init_vdpau_procs(ctx) < 0) { if (ctx->vdp->device_destroy) ctx->vdp->device_destroy(ctx->vdp_device); talloc_free(ctx); return NULL; } return ctx; } void mp_vdpau_destroy(struct mp_vdpau_ctx *ctx) { struct vdp_functions *vdp = ctx->vdp; VdpStatus vdp_st; for (int i = 0; i < MAX_VIDEO_SURFACES; i++) { // can't hold references past context lifetime assert(!ctx->video_surfaces[i].in_use); if (ctx->video_surfaces[i].surface != VDP_INVALID_HANDLE) { vdp_st = vdp->video_surface_destroy(ctx->video_surfaces[i].surface); CHECK_ST_WARNING("Error when calling vdp_video_surface_destroy"); } } if (ctx->vdp_device != VDP_INVALID_HANDLE) { vdp_st = vdp->device_destroy(ctx->vdp_device); CHECK_ST_WARNING("Error when calling vdp_device_destroy"); } talloc_free(ctx); } bool mp_vdpau_get_format(int imgfmt, VdpChromaType *out_chroma_type, VdpYCbCrFormat *out_pixel_format) { VdpChromaType chroma = VDP_CHROMA_TYPE_420; VdpYCbCrFormat ycbcr = (VdpYCbCrFormat)-1; switch (imgfmt) { case IMGFMT_420P: ycbcr = VDP_YCBCR_FORMAT_YV12; break; case IMGFMT_NV12: ycbcr = VDP_YCBCR_FORMAT_NV12; break; case IMGFMT_YUYV: ycbcr = VDP_YCBCR_FORMAT_YUYV; chroma = VDP_CHROMA_TYPE_422; break; case IMGFMT_UYVY: ycbcr = VDP_YCBCR_FORMAT_UYVY; chroma = VDP_CHROMA_TYPE_422; break; case IMGFMT_VDPAU_MPEG1: case IMGFMT_VDPAU_MPEG2: case IMGFMT_VDPAU_H264: case IMGFMT_VDPAU_WMV3: case IMGFMT_VDPAU_VC1: case IMGFMT_VDPAU_MPEG4: case IMGFMT_VDPAU: break; default: return false; } if (out_chroma_type) *out_chroma_type = chroma; if (out_pixel_format) *out_pixel_format = ycbcr; return true; }