summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorwm4 <wm4@nowhere>2018-04-20 17:48:44 +0200
committerJan Ekström <jeebjp@gmail.com>2018-04-29 02:21:32 +0300
commit76844c9c519f4366463a70c8c2366a3d5dc9046c (patch)
treeb62cb8753dea8fc16e35dc0f20679d1db95f1491
parent65f08253158ba9cb4c164a6cfd6da39b39d38e55 (diff)
downloadmpv-76844c9c519f4366463a70c8c2366a3d5dc9046c.tar.bz2
mpv-76844c9c519f4366463a70c8c2366a3d5dc9046c.tar.xz
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.
-rw-r--r--video/out/dr_helper.c130
-rw-r--r--video/out/dr_helper.h20
-rw-r--r--video/out/vo.c116
-rw-r--r--video/out/vo.h8
-rw-r--r--wscript_build.py1
5 files changed, 184 insertions, 91 deletions
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 <stdlib.h>
+#include <assert.h>
+#include <pthread.h>
+
+#include <libavutil/buffer.h>
+
+#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 <pthread.h>
#include <math.h>
-#include <libavutil/buffer.h>
-
#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
@@ -328,6 +328,14 @@ struct vo_driver {
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.
* mpi belongs to the VO; the VO must free it eventually.
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" ),