From 76844c9c519f4366463a70c8c2366a3d5dc9046c Mon Sep 17 00:00:00 2001 From: wm4 Date: Fri, 20 Apr 2018 17:48:44 +0200 Subject: vo: move DR helper code to a separate source file So it can be reused by vo_libmpv.c, which needs to use it in a slightly different way. --- video/out/dr_helper.c | 130 ++++++++++++++++++++++++++++++++++++++++++++++++++ video/out/dr_helper.h | 20 ++++++++ video/out/vo.c | 116 ++++++++++---------------------------------- video/out/vo.h | 8 ++++ wscript_build.py | 1 + 5 files changed, 184 insertions(+), 91 deletions(-) create mode 100644 video/out/dr_helper.c create mode 100644 video/out/dr_helper.h diff --git a/video/out/dr_helper.c b/video/out/dr_helper.c new file mode 100644 index 0000000000..e826d08dc4 --- /dev/null +++ b/video/out/dr_helper.c @@ -0,0 +1,130 @@ +#include +#include +#include + +#include + +#include "mpv_talloc.h" +#include "misc/dispatch.h" +#include "osdep/atomic.h" +#include "video/mp_image.h" + +#include "dr_helper.h" + +struct dr_helper { + pthread_t thread; + struct mp_dispatch_queue *dispatch; + atomic_ullong dr_in_flight; + + struct mp_image *(*get_image)(void *ctx, int imgfmt, int w, int h, + int stride_align); + void *get_image_ctx; +}; + +static void dr_helper_destroy(void *ptr) +{ + struct dr_helper *dr = ptr; + + // All references must have been freed on destruction, or we'll have + // dangling pointers. + assert(atomic_load(&dr->dr_in_flight) == 0); +} + +struct dr_helper *dr_helper_create(struct mp_dispatch_queue *dispatch, + struct mp_image *(*get_image)(void *ctx, int imgfmt, int w, int h, + int stride_align), + void *get_image_ctx) +{ + struct dr_helper *dr = talloc_ptrtype(NULL, dr); + talloc_set_destructor(dr, dr_helper_destroy); + *dr = (struct dr_helper){ + .thread = pthread_self(), + .dispatch = dispatch, + .dr_in_flight = ATOMIC_VAR_INIT(0), + .get_image = get_image, + .get_image_ctx = get_image_ctx, + }; + return dr; +} + +struct free_dr_context { + struct dr_helper *dr; + AVBufferRef *ref; +}; + +static void dr_thread_free(void *ptr) +{ + struct free_dr_context *ctx = ptr; + + unsigned long long v = atomic_fetch_add(&ctx->dr->dr_in_flight, -1); + assert(v); // value before sub is 0 - unexpected underflow. + + av_buffer_unref(&ctx->ref); + talloc_free(ctx); +} + +static void free_dr_buffer_on_dr_thread(void *opaque, uint8_t *data) +{ + struct free_dr_context *ctx = opaque; + + // The image could be unreffed even on the DR thread. In practice, this + // matters most on DR destruction. + if (pthread_equal(ctx->dr->thread, pthread_self())) { + dr_thread_free(ctx); + } else { + mp_dispatch_run(ctx->dr->dispatch, dr_thread_free, ctx); + } +} + +struct get_image_cmd { + struct dr_helper *dr; + int imgfmt, w, h, stride_align; + struct mp_image *res; +}; + +static void sync_get_image(void *ptr) +{ + struct get_image_cmd *cmd = ptr; + struct dr_helper *dr = cmd->dr; + + cmd->res = dr->get_image(dr->get_image_ctx, cmd->imgfmt, cmd->w, cmd->h, + cmd->stride_align); + if (!cmd->res) + return; + + // We require exactly 1 AVBufferRef. + assert(cmd->res->bufs[0]); + assert(!cmd->res->bufs[1]); + + // Apply some magic to get it free'd on the DR thread as well. For this to + // work, we create a dummy-ref that aliases the original ref, which is why + // the original ref must be writable in the first place. (A newly allocated + // image should be always writable of course.) + assert(mp_image_is_writeable(cmd->res)); + + struct free_dr_context *ctx = talloc_zero(NULL, struct free_dr_context); + *ctx = (struct free_dr_context){ + .dr = dr, + .ref = cmd->res->bufs[0], + }; + + AVBufferRef *new_ref = av_buffer_create(ctx->ref->data, ctx->ref->size, + free_dr_buffer_on_dr_thread, ctx, 0); + if (!new_ref) + abort(); // tiny malloc OOM + + cmd->res->bufs[0] = new_ref; + + atomic_fetch_add(&dr->dr_in_flight, 1); +} + +struct mp_image *dr_helper_get_image(struct dr_helper *dr, int imgfmt, + int w, int h, int stride_align) +{ + struct get_image_cmd cmd = { + .dr = dr, + .imgfmt = imgfmt, .w = w, .h = h, .stride_align = stride_align, + }; + mp_dispatch_run(dr->dispatch, sync_get_image, &cmd); + return cmd.res; +} diff --git a/video/out/dr_helper.h b/video/out/dr_helper.h new file mode 100644 index 0000000000..cf37c570e2 --- /dev/null +++ b/video/out/dr_helper.h @@ -0,0 +1,20 @@ +#pragma once + +// This is a helper for implementing thread-safety for DR callbacks. These need +// to allocate GPU buffers on the GPU thread (e.g. OpenGL with its forced TLS), +// and the buffers also need to be freed on the GPU thread. +struct dr_helper; + +struct mp_image; +struct mp_dispatch_queue; + +// This MUST be called on the "target" thread (it will call pthread_self()). +// dr_helper_get_image() calls will use the dispatch queue to run get_image on +// the target thread too. +struct dr_helper *dr_helper_create(struct mp_dispatch_queue *dispatch, + struct mp_image *(*get_image)(void *ctx, int imgfmt, int w, int h, + int stride_align), + void *get_image_ctx); + +struct mp_image *dr_helper_get_image(struct dr_helper *dr, int imgfmt, + int w, int h, int stride_align); diff --git a/video/out/vo.c b/video/out/vo.c index a221ed9bb9..413d8bc02c 100644 --- a/video/out/vo.c +++ b/video/out/vo.c @@ -23,8 +23,6 @@ #include #include -#include - #include "mpv_talloc.h" #include "config.h" @@ -37,6 +35,7 @@ #include "misc/bstr.h" #include "vo.h" #include "aspect.h" +#include "dr_helper.h" #include "input/input.h" #include "options/m_config.h" #include "common/msg.h" @@ -111,8 +110,7 @@ const struct vo_driver *const video_out_drivers[] = struct vo_internal { pthread_t thread; struct mp_dispatch_queue *dispatch; - - atomic_ullong dr_in_flight; + struct dr_helper *dr_helper; // --- The following fields are protected by lock pthread_mutex_t lock; @@ -1004,6 +1002,13 @@ void vo_disable_external_renderloop(struct vo *vo) in->external_renderloop_drive = false; } +static struct mp_image *get_image_vo(void *ctx, int imgfmt, int w, int h, + int stride_align) +{ + struct vo *vo = ctx; + return vo->driver->get_image(vo, imgfmt, w, h, stride_align); +} + static void *vo_thread(void *ptr) { struct vo *vo = ptr; @@ -1012,10 +1017,13 @@ static void *vo_thread(void *ptr) mpthread_set_name("vo"); + if (vo->driver->get_image) + in->dr_helper = dr_helper_create(in->dispatch, get_image_vo, vo); + int r = vo->driver->preinit(vo) ? -1 : 0; mp_rendezvous(vo, r); // init barrier if (r < 0) - return NULL; + goto done; read_opts(vo); update_display_fps(vo); @@ -1072,7 +1080,8 @@ static void *vo_thread(void *ptr) talloc_free(in->current_frame); in->current_frame = NULL; vo->driver->uninit(vo); - assert(atomic_load(&vo->in->dr_in_flight) == 0); +done: + TA_FREEP(&in->dr_helper); return NULL; } @@ -1346,6 +1355,16 @@ struct vo_frame *vo_get_current_vo_frame(struct vo *vo) return r; } +struct mp_image *vo_get_image(struct vo *vo, int imgfmt, int w, int h, + int stride_align) +{ + if (vo->driver->get_image_ts) + return vo->driver->get_image_ts(vo, imgfmt, w, h, stride_align); + if (vo->in->dr_helper) + return dr_helper_get_image(vo->in->dr_helper, imgfmt, w, h, stride_align); + return NULL; +} + static void destroy_frame(void *p) { struct vo_frame *frame = p; @@ -1385,88 +1404,3 @@ int lookup_keymap_table(const struct mp_keymap *map, int key) map++; return map->to; } - -struct free_dr_context { - struct vo *vo; - AVBufferRef *ref; -}; - -static void vo_thread_free(void *ptr) -{ - struct free_dr_context *ctx = ptr; - - unsigned long long v = atomic_fetch_add(&ctx->vo->in->dr_in_flight, -1); - assert(v); // value before sub is 0 - unexpected underflow. - - av_buffer_unref(&ctx->ref); - talloc_free(ctx); -} - -static void free_dr_buffer_on_vo_thread(void *opaque, uint8_t *data) -{ - struct free_dr_context *ctx = opaque; - - // The image could be unreffed even on the VO thread. In practice, this - // matters most on VO destruction. - if (pthread_equal(ctx->vo->in->thread, pthread_self())) { - vo_thread_free(ctx); - } else { - mp_dispatch_run(ctx->vo->in->dispatch, vo_thread_free, ctx); - } -} - -struct get_image_cmd { - struct vo *vo; - int imgfmt, w, h, stride_align; - struct mp_image *res; -}; - -static void sync_get_image(void *ptr) -{ - struct get_image_cmd *cmd = ptr; - struct vo *vo = cmd->vo; - - cmd->res = vo->driver->get_image(vo, cmd->imgfmt, cmd->w, cmd->h, - cmd->stride_align); - if (!cmd->res) - return; - - // We require exactly 1 AVBufferRef. - assert(cmd->res->bufs[0]); - assert(!cmd->res->bufs[1]); - - // Apply some magic to get it free'd on the VO thread as well. For this to - // work, we create a dummy-ref that aliases the original ref, which is why - // the original ref must be writable in the first place. (A newly allocated - // image should be always writable of course.) - assert(mp_image_is_writeable(cmd->res)); - - struct free_dr_context *ctx = talloc_zero(NULL, struct free_dr_context); - *ctx = (struct free_dr_context){ - .vo = vo, - .ref = cmd->res->bufs[0], - }; - - AVBufferRef *new_ref = av_buffer_create(ctx->ref->data, ctx->ref->size, - free_dr_buffer_on_vo_thread, ctx, 0); - if (!new_ref) - abort(); // tiny malloc OOM - - cmd->res->bufs[0] = new_ref; - - atomic_fetch_add(&vo->in->dr_in_flight, 1); -} - -struct mp_image *vo_get_image(struct vo *vo, int imgfmt, int w, int h, - int stride_align) -{ - if (!vo->driver->get_image) - return NULL; - - struct get_image_cmd cmd = { - .vo = vo, - .imgfmt = imgfmt, .w = w, .h = h, .stride_align = stride_align, - }; - mp_dispatch_run(vo->in->dispatch, sync_get_image, &cmd); - return cmd.res; -} diff --git a/video/out/vo.h b/video/out/vo.h index 4a54914962..947493268c 100644 --- a/video/out/vo.h +++ b/video/out/vo.h @@ -327,6 +327,14 @@ struct vo_driver { struct mp_image *(*get_image)(struct vo *vo, int imgfmt, int w, int h, int stride_align); + /* + * Thread-safe variant of get_image. Set at most one of these callbacks. + * This excludes _all_ synchronization magic. The only guarantee is that + * vo_driver.uninit is not called before this function returns. + */ + struct mp_image *(*get_image_ts)(struct vo *vo, int imgfmt, int w, int h, + int stride_align); + /* * Render the given frame to the VO's backbuffer. This operation will be * followed by a draw_osd and a flip_page[_timed] call. diff --git a/wscript_build.py b/wscript_build.py index 4d93f2b498..2329a3f8ef 100644 --- a/wscript_build.py +++ b/wscript_build.py @@ -428,6 +428,7 @@ def build(ctx): ( "video/out/d3d11/hwdec_dxva2dxgi.c", "d3d11 && d3d9-hwaccel" ), ( "video/out/d3d11/ra_d3d11.c", "d3d11" ), ( "video/out/dither.c" ), + ( "video/out/dr_helper.c" ), ( "video/out/drm_atomic.c", "drm" ), ( "video/out/drm_common.c", "drm" ), ( "video/out/drm_prime.c", "drm && drmprime" ), -- cgit v1.2.3