mirror of https://github.com/mpv-player/mpv
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.
This commit is contained in:
parent
65f0825315
commit
76844c9c51
|
@ -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;
|
||||||
|
}
|
|
@ -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);
|
116
video/out/vo.c
116
video/out/vo.c
|
@ -23,8 +23,6 @@
|
||||||
#include <pthread.h>
|
#include <pthread.h>
|
||||||
#include <math.h>
|
#include <math.h>
|
||||||
|
|
||||||
#include <libavutil/buffer.h>
|
|
||||||
|
|
||||||
#include "mpv_talloc.h"
|
#include "mpv_talloc.h"
|
||||||
|
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
|
@ -37,6 +35,7 @@
|
||||||
#include "misc/bstr.h"
|
#include "misc/bstr.h"
|
||||||
#include "vo.h"
|
#include "vo.h"
|
||||||
#include "aspect.h"
|
#include "aspect.h"
|
||||||
|
#include "dr_helper.h"
|
||||||
#include "input/input.h"
|
#include "input/input.h"
|
||||||
#include "options/m_config.h"
|
#include "options/m_config.h"
|
||||||
#include "common/msg.h"
|
#include "common/msg.h"
|
||||||
|
@ -111,8 +110,7 @@ const struct vo_driver *const video_out_drivers[] =
|
||||||
struct vo_internal {
|
struct vo_internal {
|
||||||
pthread_t thread;
|
pthread_t thread;
|
||||||
struct mp_dispatch_queue *dispatch;
|
struct mp_dispatch_queue *dispatch;
|
||||||
|
struct dr_helper *dr_helper;
|
||||||
atomic_ullong dr_in_flight;
|
|
||||||
|
|
||||||
// --- The following fields are protected by lock
|
// --- The following fields are protected by lock
|
||||||
pthread_mutex_t lock;
|
pthread_mutex_t lock;
|
||||||
|
@ -1004,6 +1002,13 @@ void vo_disable_external_renderloop(struct vo *vo)
|
||||||
in->external_renderloop_drive = false;
|
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)
|
static void *vo_thread(void *ptr)
|
||||||
{
|
{
|
||||||
struct vo *vo = ptr;
|
struct vo *vo = ptr;
|
||||||
|
@ -1012,10 +1017,13 @@ static void *vo_thread(void *ptr)
|
||||||
|
|
||||||
mpthread_set_name("vo");
|
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;
|
int r = vo->driver->preinit(vo) ? -1 : 0;
|
||||||
mp_rendezvous(vo, r); // init barrier
|
mp_rendezvous(vo, r); // init barrier
|
||||||
if (r < 0)
|
if (r < 0)
|
||||||
return NULL;
|
goto done;
|
||||||
|
|
||||||
read_opts(vo);
|
read_opts(vo);
|
||||||
update_display_fps(vo);
|
update_display_fps(vo);
|
||||||
|
@ -1072,7 +1080,8 @@ static void *vo_thread(void *ptr)
|
||||||
talloc_free(in->current_frame);
|
talloc_free(in->current_frame);
|
||||||
in->current_frame = NULL;
|
in->current_frame = NULL;
|
||||||
vo->driver->uninit(vo);
|
vo->driver->uninit(vo);
|
||||||
assert(atomic_load(&vo->in->dr_in_flight) == 0);
|
done:
|
||||||
|
TA_FREEP(&in->dr_helper);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1346,6 +1355,16 @@ struct vo_frame *vo_get_current_vo_frame(struct vo *vo)
|
||||||
return r;
|
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)
|
static void destroy_frame(void *p)
|
||||||
{
|
{
|
||||||
struct vo_frame *frame = p;
|
struct vo_frame *frame = p;
|
||||||
|
@ -1385,88 +1404,3 @@ int lookup_keymap_table(const struct mp_keymap *map, int key)
|
||||||
map++;
|
map++;
|
||||||
return map->to;
|
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;
|
|
||||||
}
|
|
||||||
|
|
|
@ -327,6 +327,14 @@ struct vo_driver {
|
||||||
struct mp_image *(*get_image)(struct vo *vo, int imgfmt, int w, int h,
|
struct mp_image *(*get_image)(struct vo *vo, int imgfmt, int w, int h,
|
||||||
int stride_align);
|
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
|
* Render the given frame to the VO's backbuffer. This operation will be
|
||||||
* followed by a draw_osd and a flip_page[_timed] call.
|
* followed by a draw_osd and a flip_page[_timed] call.
|
||||||
|
|
|
@ -428,6 +428,7 @@ def build(ctx):
|
||||||
( "video/out/d3d11/hwdec_dxva2dxgi.c", "d3d11 && d3d9-hwaccel" ),
|
( "video/out/d3d11/hwdec_dxva2dxgi.c", "d3d11 && d3d9-hwaccel" ),
|
||||||
( "video/out/d3d11/ra_d3d11.c", "d3d11" ),
|
( "video/out/d3d11/ra_d3d11.c", "d3d11" ),
|
||||||
( "video/out/dither.c" ),
|
( "video/out/dither.c" ),
|
||||||
|
( "video/out/dr_helper.c" ),
|
||||||
( "video/out/drm_atomic.c", "drm" ),
|
( "video/out/drm_atomic.c", "drm" ),
|
||||||
( "video/out/drm_common.c", "drm" ),
|
( "video/out/drm_common.c", "drm" ),
|
||||||
( "video/out/drm_prime.c", "drm && drmprime" ),
|
( "video/out/drm_prime.c", "drm && drmprime" ),
|
||||||
|
|
Loading…
Reference in New Issue