diff --git a/video/vdpau.c b/video/vdpau.c index b9109c36a8..e59befebdc 100644 --- a/video/vdpau.c +++ b/video/vdpau.c @@ -19,6 +19,7 @@ #include "vdpau.h" +#include "osdep/threads.h" #include "osdep/timer.h" #include "video/out/x11_common.h" @@ -35,7 +36,10 @@ static void mark_vdpau_objects_uninitialized(struct mp_vdpau_ctx *ctx) static void preemption_callback(VdpDevice device, void *context) { struct mp_vdpau_ctx *ctx = context; + + pthread_mutex_lock(&ctx->preempt_lock); ctx->is_preempted = true; + pthread_mutex_unlock(&ctx->preempt_lock); } static int win_x11_init_vdpau_procs(struct mp_vdpau_ctx *ctx) @@ -130,32 +134,52 @@ static int handle_preemption(struct mp_vdpau_ctx *ctx) // 1: everything is fine, no preemption happened int mp_vdpau_handle_preemption(struct mp_vdpau_ctx *ctx, uint64_t *counter) { + int r = 1; + pthread_mutex_lock(&ctx->preempt_lock); + // First time init if (!*counter) *counter = ctx->preemption_counter; if (handle_preemption(ctx) < 0) - return -1; + r = -1; - if (*counter < ctx->preemption_counter) { + if (r > 0 && *counter < ctx->preemption_counter) { *counter = ctx->preemption_counter; - return 0; // signal recovery after preemption + r = 0; // signal recovery after preemption } - return 1; + + pthread_mutex_unlock(&ctx->preempt_lock); + return r; } +struct surface_ref { + struct mp_vdpau_ctx *ctx; + int index; +}; + static void release_decoder_surface(void *ptr) { - bool *in_use_ptr = ptr; - *in_use_ptr = false; + struct surface_ref *r = ptr; + struct mp_vdpau_ctx *ctx = r->ctx; + + pthread_mutex_lock(&ctx->pool_lock); + assert(ctx->video_surfaces[r->index].in_use); + ctx->video_surfaces[r->index].in_use = false; + pthread_mutex_unlock(&ctx->pool_lock); + + talloc_free(r); } -static struct mp_image *create_ref(struct surface_entry *e) +static struct mp_image *create_ref(struct mp_vdpau_ctx *ctx, int index) { + struct surface_entry *e = &ctx->video_surfaces[index]; assert(!e->in_use); e->in_use = true; + struct surface_ref *ref = talloc_ptrtype(NULL, ref); + *ref = (struct surface_ref){ctx, index}; struct mp_image *res = - mp_image_new_custom_ref(&(struct mp_image){0}, &e->in_use, + mp_image_new_custom_ref(&(struct mp_image){0}, ref, release_decoder_surface); mp_image_setfmt(res, IMGFMT_VDPAU); mp_image_set_size(res, e->w, e->h); @@ -168,8 +192,11 @@ struct mp_image *mp_vdpau_get_video_surface(struct mp_vdpau_ctx *ctx, VdpChromaType chroma, int w, int h) { struct vdp_functions *vdp = &ctx->vdp; + int surface_index = -1; VdpStatus vdp_st; + pthread_mutex_lock(&ctx->pool_lock); + // 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]; @@ -188,7 +215,8 @@ struct mp_image *mp_vdpau_get_video_surface(struct mp_vdpau_ctx *ctx, if (!e->in_use && e->surface != VDP_INVALID_HANDLE) { assert(e->w == w && e->h == h); assert(e->chroma == chroma); - return create_ref(e); + surface_index = n; + goto done; } } @@ -203,12 +231,21 @@ struct mp_image *mp_vdpau_get_video_surface(struct mp_vdpau_ctx *ctx, vdp_st = vdp->video_surface_create(ctx->vdp_device, chroma, w, h, &e->surface); CHECK_VDP_WARNING(ctx, "Error when calling vdp_video_surface_create"); - return create_ref(e); + surface_index = n; + goto done; } } - MP_ERR(ctx, "no surfaces available in mp_vdpau_get_video_surface\n"); - return NULL; +done: ; + struct mp_image *mpi = NULL; + if (surface_index >= 0) + mpi = create_ref(ctx, surface_index); + + pthread_mutex_unlock(&ctx->pool_lock); + + if (!mpi) + MP_ERR(ctx, "no surfaces available in mp_vdpau_get_video_surface\n"); + return mpi; } struct mp_vdpau_ctx *mp_vdpau_create_device_x11(struct mp_log *log, @@ -220,13 +257,13 @@ struct mp_vdpau_ctx *mp_vdpau_create_device_x11(struct mp_log *log, .x11 = x11, .preemption_counter = 1, }; + mpthread_mutex_init_recursive(&ctx->preempt_lock); + pthread_mutex_init(&ctx->pool_lock, NULL); 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); + mp_vdpau_destroy(ctx); return NULL; } return ctx; @@ -246,11 +283,13 @@ void mp_vdpau_destroy(struct mp_vdpau_ctx *ctx) } } - if (ctx->vdp_device != VDP_INVALID_HANDLE) { + if (vdp->device_destroy && ctx->vdp_device != VDP_INVALID_HANDLE) { vdp_st = vdp->device_destroy(ctx->vdp_device); CHECK_VDP_WARNING(ctx, "Error when calling vdp_device_destroy"); } + pthread_mutex_destroy(&ctx->pool_lock); + pthread_mutex_destroy(&ctx->preempt_lock); talloc_free(ctx); } diff --git a/video/vdpau.h b/video/vdpau.h index 38e679db25..acf4280266 100644 --- a/video/vdpau.h +++ b/video/vdpau.h @@ -4,6 +4,8 @@ #include #include +#include + #include #include @@ -35,26 +37,31 @@ struct vdp_functions { // Shared state. Objects created from different VdpDevices are often (always?) // incompatible to each other, so all code must use a shared VdpDevice. struct mp_vdpau_ctx { + struct mp_log *log; + struct vo_x11_state *x11; + + // These are mostly immutable, except on preemption. We don't really care + // to synchronize the preemption case fully correctly, because it's an + // extremely obscure corner case, and basically a vdpau API design bug. + // What we do will sort-of work anyway (no memory errors are possible). struct vdp_functions vdp; VdpGetProcAddress *get_proc_address; VdpDevice vdp_device; + + pthread_mutex_t preempt_lock; bool is_preempted; // set to true during unavailability uint64_t preemption_counter; // incremented after _restoring_ - bool preemption_user_notified; double last_preemption_retry_fail; - struct vo_x11_state *x11; - // Surface pool + pthread_mutex_t pool_lock; struct surface_entry { VdpVideoSurface surface; int w, h; VdpChromaType chroma; bool in_use; } video_surfaces[MAX_VIDEO_SURFACES]; - - struct mp_log *log; }; struct mp_vdpau_ctx *mp_vdpau_create_device_x11(struct mp_log *log,