summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorwm4 <wm4@nowhere>2017-07-16 18:47:59 +0200
committerwm4 <wm4@nowhere>2017-07-16 18:48:12 +0200
commit88f06eb126b023410f9ce12061ddd5db82a4e73b (patch)
treeb806472feb34dff8cbb5ecf2bf4878067b2e41e6
parent44391af7df57cf5e1cafda53ac998e7b55c51b92 (diff)
downloadmpv-88f06eb126b023410f9ce12061ddd5db82a4e73b.tar.bz2
mpv-88f06eb126b023410f9ce12061ddd5db82a4e73b.tar.xz
implement half-working DR
-rw-r--r--player/video.c1
-rw-r--r--video/decode/dec_video.h1
-rw-r--r--video/decode/vd_lavc.c58
-rw-r--r--video/mp_image.c54
-rw-r--r--video/mp_image.h4
-rw-r--r--video/mp_image_pool.c17
-rw-r--r--video/mp_image_pool.h2
-rw-r--r--video/out/opengl/common.c8
-rw-r--r--video/out/opengl/common.h2
-rw-r--r--video/out/opengl/context.c1
-rw-r--r--video/out/opengl/gl_headers.h6
-rw-r--r--video/out/opengl/video.c94
-rw-r--r--video/out/opengl/video.h3
-rw-r--r--video/out/vo.c194
-rw-r--r--video/out/vo.h42
-rw-r--r--video/out/vo_opengl.c31
16 files changed, 500 insertions, 18 deletions
diff --git a/player/video.c b/player/video.c
index c1aee44674..108d65a35b 100644
--- a/player/video.c
+++ b/player/video.c
@@ -425,6 +425,7 @@ int init_video_decoder(struct MPContext *mpctx, struct track *track)
d_video->header = track->stream;
d_video->codec = track->stream->codec;
d_video->fps = d_video->header->codec->fps;
+ d_video->vo = mpctx->vo_chain->vo;
// Note: at least mpv_opengl_cb_uninit_gl() relies on being able to get
// rid of all references to the VO by destroying the VO chain. Thus,
diff --git a/video/decode/dec_video.h b/video/decode/dec_video.h
index 8d1936e016..a647de01dd 100644
--- a/video/decode/dec_video.h
+++ b/video/decode/dec_video.h
@@ -37,6 +37,7 @@ struct dec_video {
struct mp_hwdec_devices *hwdec_devs; // video output hwdec handles
struct sh_stream *header;
struct mp_codec_params *codec;
+ struct vo *vo; // required for direct rendering into video memory
char *decoder_desc;
diff --git a/video/decode/vd_lavc.c b/video/decode/vd_lavc.c
index 27861171f5..cf1b059f5b 100644
--- a/video/decode/vd_lavc.c
+++ b/video/decode/vd_lavc.c
@@ -57,6 +57,7 @@
#include "demux/packet.h"
#include "video/csputils.h"
#include "video/sws_utils.h"
+#include "video/out/vo.h"
#if LIBAVCODEC_VERSION_MICRO >= 100
#include <libavutil/mastering_display_metadata.h>
@@ -74,6 +75,7 @@ static void init_avctx(struct dec_video *vd, const char *decoder,
struct vd_lavc_hwdec *hwdec);
static void uninit_avctx(struct dec_video *vd);
+static int get_buffer2_direct(AVCodecContext *avctx, AVFrame *pic, int flags);
static int get_buffer2_hwdec(AVCodecContext *avctx, AVFrame *pic, int flags);
static enum AVPixelFormat get_format_hwdec(struct AVCodecContext *avctx,
const enum AVPixelFormat *pix_fmt);
@@ -597,6 +599,12 @@ static void init_avctx(struct dec_video *vd, const char *decoder,
mp_set_avcodec_threads(vd->log, avctx, lavc_param->threads);
}
+ if (!ctx->hwdec && vd->vo && vd->vo->driver->get_image) {
+ avctx->opaque = vd;
+ avctx->get_buffer2 = get_buffer2_direct;
+ avctx->thread_safe_callbacks = 1;
+ }
+
avctx->flags |= lavc_param->bitexact ? AV_CODEC_FLAG_BITEXACT : 0;
avctx->flags2 |= lavc_param->fast ? AV_CODEC_FLAG2_FAST : 0;
@@ -917,6 +925,56 @@ static enum AVPixelFormat get_format_hwdec(struct AVCodecContext *avctx,
return select;
}
+static int get_buffer2_direct(AVCodecContext *avctx, AVFrame *pic, int flags)
+{
+ struct dec_video *vd = avctx->opaque;
+
+ assert(vd->vo);
+
+ int w = pic->width;
+ int h = pic->height;
+ int linesize_align[AV_NUM_DATA_POINTERS];
+ avcodec_align_dimensions2(avctx, &w, &h, linesize_align);
+
+ // We assume that different alignments are just different power-of-2s.
+ // Thus, a higher alignment always satisfies a lower alignment.
+ int stride_align = 0;
+ for (int n = 0; n < AV_NUM_DATA_POINTERS; n++)
+ stride_align = MPMAX(stride_align, linesize_align[n]);
+
+ int imgfmt = pixfmt2imgfmt(pic->format);
+ if (!imgfmt)
+ goto fallback;
+
+ struct mp_image *img = vo_get_image(vd->vo, imgfmt, w, h, stride_align);
+ if (!img)
+ goto fallback;
+
+ /*
+
+ AVFrame *new = mp_image_to_av_frame_and_unref(img);
+ if (!new)
+ goto fallback;
+
+ av_frame_move_ref(pic, new);
+ av_frame_free(&new);
+ */
+ // get_buffer2 callers seem very unappreciative of overwriting pic with a
+ // new reference, so move back the data only manually.
+ for (int n = 0; n < 4; n++) {
+ pic->data[n] = img->planes[n];
+ pic->linesize[n] = img->stride[n];
+ pic->buf[n] = img->bufs[n];
+ img->bufs[n] = NULL;
+ }
+ talloc_free(img);
+
+ return 0;
+
+fallback:
+ return avcodec_default_get_buffer2(avctx, pic, flags);
+}
+
static int get_buffer2_hwdec(AVCodecContext *avctx, AVFrame *pic, int flags)
{
struct dec_video *vd = avctx->opaque;
diff --git a/video/mp_image.c b/video/mp_image.c
index 281376b5f1..02ebf9c3ac 100644
--- a/video/mp_image.c
+++ b/video/mp_image.c
@@ -41,40 +41,64 @@
#define HAVE_OPAQUE_REF (LIBAVUTIL_VERSION_MICRO >= 100 && \
LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(55, 47, 100))
-static bool mp_image_alloc_planes(struct mp_image *mpi)
+// Determine strides, plane sizes, and total required size for an image
+// allocation. Returns total size on success, <0 on error. Unused planes
+// are always set to 0 up until MP_MAX_PLANES-1.
+int mp_image_layout(int imgfmt, int w, int h, int stride_align,
+ int out_stride[MP_MAX_PLANES],
+ int out_plane_size[MP_MAX_PLANES])
{
- assert(!mpi->planes[0]);
- assert(!mpi->bufs[0]);
+ struct mp_imgfmt_desc desc = mp_imgfmt_get_desc(imgfmt);
+ struct mp_image_params params = {.imgfmt = imgfmt, .w = w, .h = h};
- if (!mp_image_params_valid(&mpi->params) || mpi->fmt.flags & MP_IMGFLAG_HWACCEL)
- return false;
+ if (!mp_image_params_valid(&params) || desc.flags & MP_IMGFLAG_HWACCEL)
+ return -1;
// Note: for non-mod-2 4:2:0 YUV frames, we have to allocate an additional
// top/right border. This is needed for correct handling of such
// images in filter and VO code (e.g. vo_vdpau or vo_opengl).
- size_t plane_size[MP_MAX_PLANES];
for (int n = 0; n < MP_MAX_PLANES; n++) {
- int alloc_h = MP_ALIGN_UP(mpi->h, 32) >> mpi->fmt.ys[n];
- int line_bytes = (mp_image_plane_w(mpi, n) * mpi->fmt.bpp[n] + 7) / 8;
- mpi->stride[n] = FFALIGN(line_bytes, SWS_MIN_BYTE_ALIGN);
- plane_size[n] = mpi->stride[n] * alloc_h;
+ int alloc_w = mp_chroma_div_up(w, desc.xs[n]);
+ int alloc_h = MP_ALIGN_UP(h, 32) >> desc.ys[n];
+ int line_bytes = (alloc_w * desc.bpp[n] + 7) / 8;
+ out_stride[n] = FFALIGN(line_bytes, stride_align);
+ out_plane_size[n] = out_stride[n] * alloc_h;
}
- if (mpi->fmt.flags & MP_IMGFLAG_PAL)
- plane_size[1] = MP_PALETTE_SIZE;
+ if (desc.flags & MP_IMGFLAG_PAL)
+ out_plane_size[1] = MP_PALETTE_SIZE;
- size_t sum = 0;
+ int sum = 0;
for (int n = 0; n < MP_MAX_PLANES; n++)
- sum += plane_size[n];
+ sum += out_plane_size[n];
+
+ return sum;
+}
+
+static bool mp_image_alloc_planes(struct mp_image *mpi)
+{
+ assert(!mpi->planes[0]);
+ assert(!mpi->bufs[0]);
+
+ if (!mp_image_params_valid(&mpi->params) || mpi->fmt.flags & MP_IMGFLAG_HWACCEL)
+ return false;
+
+ int stride[MP_MAX_PLANES];
+ int plane_size[MP_MAX_PLANES];
+ int size = mp_image_layout(mpi->imgfmt, mpi->w, mpi->h, SWS_MIN_BYTE_ALIGN,
+ stride, plane_size);
+ if (size < 0)
+ return false;
// Note: mp_image_pool assumes this creates only 1 AVBufferRef.
- mpi->bufs[0] = av_buffer_alloc(FFMAX(sum, 1));
+ mpi->bufs[0] = av_buffer_alloc(FFMAX(size, 1));
if (!mpi->bufs[0])
return false;
uint8_t *data = mpi->bufs[0]->data;
for (int n = 0; n < MP_MAX_PLANES; n++) {
mpi->planes[n] = plane_size[n] ? data : NULL;
+ mpi->stride[n] = stride[n];
data += plane_size[n];
}
return true;
diff --git a/video/mp_image.h b/video/mp_image.h
index 56ce2257f4..8b2e9483c2 100644
--- a/video/mp_image.h
+++ b/video/mp_image.h
@@ -104,6 +104,10 @@ typedef struct mp_image {
int mp_chroma_div_up(int size, int shift);
+int mp_image_layout(int imgfmt, int w, int h, int stride_align,
+ int out_stride[MP_MAX_PLANES],
+ int out_plane_size[MP_MAX_PLANES]);
+
struct mp_image *mp_image_alloc(int fmt, int w, int h);
void mp_image_copy(struct mp_image *dmpi, struct mp_image *mpi);
void mp_image_copy_gpu(struct mp_image *dst, struct mp_image *src);
diff --git a/video/mp_image_pool.c b/video/mp_image_pool.c
index 9a848af925..7b1ffb1d59 100644
--- a/video/mp_image_pool.c
+++ b/video/mp_image_pool.c
@@ -97,6 +97,23 @@ void mp_image_pool_clear(struct mp_image_pool *pool)
pool->num_images = 0;
}
+// Return number of images still referenced (but not free ones).
+// Racy, useful for debugging.
+int mp_image_pool_get_in_use(struct mp_image_pool *pool)
+{
+ int in_use = 0;
+ for (int n = 0; n < pool->num_images; n++) {
+ struct mp_image *img = pool->images[n];
+ struct image_flags *it = img->priv;
+ pool_lock();
+ assert(it->pool_alive);
+ if (it->referenced)
+ in_use++;
+ pool_unlock();
+ }
+ return in_use;
+}
+
// This is the only function that is allowed to run in a different thread.
// (Consider passing an image to another thread, which frees it.)
static void unref_image(void *opaque, uint8_t *data)
diff --git a/video/mp_image_pool.h b/video/mp_image_pool.h
index 95e4ae57be..acadc9e88b 100644
--- a/video/mp_image_pool.h
+++ b/video/mp_image_pool.h
@@ -12,6 +12,8 @@ struct mp_image *mp_image_pool_get(struct mp_image_pool *pool, int fmt,
void mp_image_pool_add(struct mp_image_pool *pool, struct mp_image *new);
void mp_image_pool_clear(struct mp_image_pool *pool);
+int mp_image_pool_get_in_use(struct mp_image_pool *pool);
+
void mp_image_pool_set_lru(struct mp_image_pool *pool);
struct mp_image *mp_image_pool_get_no_alloc(struct mp_image_pool *pool, int fmt,
diff --git a/video/out/opengl/common.c b/video/out/opengl/common.c
index 203c14b7ef..c7eee414ac 100644
--- a/video/out/opengl/common.c
+++ b/video/out/opengl/common.c
@@ -327,6 +327,14 @@ static const struct gl_functions gl_functions[] = {
{0}
},
},
+ {
+ .ver_core = 440,
+ .extension = "GL_ARB_buffer_storage",
+ .functions = (const struct gl_function[]) {
+ DEF_FN(BufferStorage),
+ {0}
+ },
+ },
// Swap control, always an OS specific extension
// The OSX code loads this manually.
{
diff --git a/video/out/opengl/common.h b/video/out/opengl/common.h
index c9162f2479..7842c5a910 100644
--- a/video/out/opengl/common.h
+++ b/video/out/opengl/common.h
@@ -192,6 +192,8 @@ struct GL {
GLenum (GLAPIENTRY *ClientWaitSync)(GLsync, GLbitfield, GLuint64);
void (GLAPIENTRY *DeleteSync)(GLsync sync);
+ void (GLAPIENTRY *BufferStorage)(GLenum, intptr_t, const GLvoid *, GLenum);
+
void (GLAPIENTRY *GenQueries)(GLsizei, GLuint *);
void (GLAPIENTRY *DeleteQueries)(GLsizei, const GLuint *);
void (GLAPIENTRY *BeginQuery)(GLenum, GLuint);
diff --git a/video/out/opengl/context.c b/video/out/opengl/context.c
index 20b16b73ef..ab98eddbf9 100644
--- a/video/out/opengl/context.c
+++ b/video/out/opengl/context.c
@@ -92,6 +92,7 @@ static const struct mpgl_driver *const backends[] = {
// 0-terminated list of desktop GL versions a backend should try to
// initialize. The first entry is the most preferred version.
const int mpgl_preferred_gl_versions[] = {
+ 440,
400,
330,
320,
diff --git a/video/out/opengl/gl_headers.h b/video/out/opengl/gl_headers.h
index bfefc3d3bf..c2214f36a3 100644
--- a/video/out/opengl/gl_headers.h
+++ b/video/out/opengl/gl_headers.h
@@ -70,6 +70,12 @@
#define GL_DEBUG_SEVERITY_LOW 0x9148
#define GL_DEBUG_SEVERITY_NOTIFICATION 0x826B
+// --- GL 4.4 or GL_ARB_buffer_storage
+#define GL_MAP_PERSISTENT_BIT 0x0040
+#define GL_MAP_COHERENT_BIT 0x0080
+#define GL_DYNAMIC_STORAGE_BIT 0x0100
+#define GL_CLIENT_STORAGE_BIT 0x0200
+
// --- GL_NV_vdpau_interop
#define GLvdpauSurfaceNV GLintptr
diff --git a/video/out/opengl/video.c b/video/out/opengl/video.c
index 50e70ce08f..9507637f7b 100644
--- a/video/out/opengl/video.c
+++ b/video/out/opengl/video.c
@@ -174,6 +174,12 @@ struct pass_info {
#define PASS_INFO_MAX (SHADER_MAX_HOOKS + 32)
+struct dr_buffer {
+ void *ptr;
+ size_t size;
+ GLuint pbo;
+};
+
struct gl_video {
GL *gl;
@@ -214,6 +220,9 @@ struct gl_video {
struct video_image image;
+ struct dr_buffer *dr_buffers;
+ int num_dr_buffers;
+
bool dumb_mode;
bool forced_dumb_mode;
@@ -3105,11 +3114,34 @@ static bool pass_upload_image(struct gl_video *p, struct mp_image *mpi, uint64_t
plane->flipped = mpi->stride[0] < 0;
+ struct dr_buffer *mapped = NULL;
+ for (int i = 0; i < p->num_dr_buffers; i++) {
+ struct dr_buffer *buffer = &p->dr_buffers[i];
+ if (mpi->planes[n] >= (uint8_t *)buffer->ptr &&
+ mpi->planes[n] < (uint8_t *)buffer->ptr + buffer->size)
+ {
+ mapped = buffer;
+ break;
+ }
+ }
+
gl->BindTexture(plane->gl_target, plane->gl_texture);
- gl_pbo_upload_tex(&plane->pbo, gl, p->opts.pbo, plane->gl_target,
- plane->gl_format, plane->gl_type, plane->w, plane->h,
- mpi->planes[n], mpi->stride[n],
+ if (mapped) {
+ assert(mapped->pbo > 0);
+ gl->BindBuffer(GL_PIXEL_UNPACK_BUFFER, mapped->pbo);
+ uintptr_t offset = mpi->planes[n] - (uint8_t *)mapped->ptr;
+ gl_upload_tex(gl, plane->gl_target,
+ plane->gl_format, plane->gl_type,
+ (void *)offset, mpi->stride[n],
0, 0, plane->w, plane->h);
+ gl->BindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
+ } else {
+ MP_WARN(p, "non-DR path!\n");
+ gl_pbo_upload_tex(&plane->pbo, gl, p->opts.pbo, plane->gl_target,
+ plane->gl_format, plane->gl_type, plane->w, plane->h,
+ mpi->planes[n], mpi->stride[n],
+ 0, 0, plane->w, plane->h);
+ }
gl->BindTexture(plane->gl_target, 0);
}
gl_timer_stop(gl);
@@ -3341,6 +3373,9 @@ void gl_video_uninit(struct gl_video *p)
gl_set_debug_logger(gl, NULL);
+ // Should all have been unreffed already.
+ assert(!p->num_dr_buffers);
+
talloc_free(p);
}
@@ -3625,3 +3660,56 @@ void gl_video_set_hwdec(struct gl_video *p, struct gl_hwdec *hwdec)
p->hwdec = hwdec;
unref_current_image(p);
}
+
+void *gl_video_dr_alloc_buffer(struct gl_video *p, size_t size)
+{
+ GL *gl = p->gl;
+
+ if (gl->version < 440)
+ return NULL;
+
+ MP_TARRAY_GROW(p, p->dr_buffers, p->num_dr_buffers);
+ int index = p->num_dr_buffers++;
+ struct dr_buffer *buffer = &p->dr_buffers[index];
+
+ *buffer = (struct dr_buffer){
+ .size = size,
+ };
+
+ unsigned flags = GL_MAP_READ_BIT | GL_MAP_WRITE_BIT | GL_MAP_PERSISTENT_BIT |
+ GL_MAP_COHERENT_BIT;
+
+ gl->GenBuffers(1, &buffer->pbo);
+ gl->BindBuffer(GL_PIXEL_UNPACK_BUFFER, buffer->pbo);
+ gl->BufferStorage(GL_PIXEL_UNPACK_BUFFER, size, NULL, flags);
+ buffer->ptr = gl->MapBufferRange(GL_PIXEL_UNPACK_BUFFER, 0, size, flags);
+ gl->BindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
+ if (!buffer->ptr) {
+ gl_check_error(p->gl, p->log, "mapping buffer");
+ gl->DeleteBuffers(1, &buffer->pbo);
+ MP_TARRAY_REMOVE_AT(p->dr_buffers, p->num_dr_buffers, index);
+ return NULL;
+ }
+
+ return buffer->ptr;
+};
+
+void gl_video_dr_free_buffer(struct gl_video *p, void *ptr)
+{
+ GL *gl = p->gl;
+
+ for (int n = 0; n < p->num_dr_buffers; n++) {
+ struct dr_buffer *buffer = &p->dr_buffers[n];
+ if (buffer->ptr == ptr) {
+ gl->BindBuffer(GL_PIXEL_UNPACK_BUFFER, buffer->pbo);
+ gl->UnmapBuffer(GL_PIXEL_UNPACK_BUFFER);
+ gl->BindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
+ gl->DeleteBuffers(1, &buffer->pbo);
+
+ MP_TARRAY_REMOVE_AT(p->dr_buffers, p->num_dr_buffers, n);
+ return;
+ }
+ }
+ // not found - that must not happen
+ assert(0);
+}
diff --git a/video/out/opengl/video.h b/video/out/opengl/video.h
index 09083da41b..f3608626e4 100644
--- a/video/out/opengl/video.h
+++ b/video/out/opengl/video.h
@@ -182,4 +182,7 @@ void gl_video_set_hwdec(struct gl_video *p, struct gl_hwdec *hwdec);
struct vo;
void gl_video_configure_queue(struct gl_video *p, struct vo *vo);
+void *gl_video_dr_alloc_buffer(struct gl_video *p, size_t size);
+void gl_video_dr_free_buffer(struct gl_video *p, void *ptr);
+
#endif
diff --git a/video/out/vo.c b/video/out/vo.c
index 79fc4f3bb4..e845bc9b23 100644
--- a/video/out/vo.c
+++ b/video/out/vo.c
@@ -1246,6 +1246,15 @@ struct vo_frame *vo_frame_ref(struct vo_frame *frame)
return new;
}
+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;
+
+ return vo->driver->get_image(vo, imgfmt, w, h, stride_align);
+}
+
/*
* lookup an integer in a table, table must have 0 as the last key
* param: key key to search for
@@ -1258,3 +1267,188 @@ int lookup_keymap_table(const struct mp_keymap *map, int key)
map++;
return map->to;
}
+
+#include <libavutil/buffer.h>
+
+#include "video/mp_image_pool.h"
+
+struct vo_synced_pool {
+ struct vo *vo;
+
+ void *user_opaque;
+ void *(*get_buffer)(void *user_opaque, size_t size);
+ void (*free_buffer)(void *user_opaque, void *ptr);
+
+
+ // --- The following fields are protected by lock
+ pthread_mutex_t lock;
+ int pool_w, pool_h, pool_align, pool_imgfmt;
+ struct mp_image_pool *pool;
+};
+
+static void synced_pool_free(void *ptr)
+{
+ struct vo_synced_pool *p = ptr;
+
+ // We don't support keeping references from the pool after the pool was
+ // destroyed. While mp_image_pool normally supports this, the user's
+ // free_buffer callback won't.
+ int num = mp_image_pool_get_in_use(p->pool);
+ if (num)
+ fprintf(stderr, "in use: %d \n", num);
+ assert(!mp_image_pool_get_in_use(p->pool));
+
+ // This will also call the user's free_buffer for the remaining images.
+ talloc_free(p->pool);
+
+ pthread_mutex_destroy(&p->lock);
+}
+
+// This provides a thread-safe pool for allocating data from a thread-unsafe VO.
+// The provided callbacks will always be run on the VO (by blocking on the VO).
+// Doing the blocking/synchronization is the reason why it wants a VO pointer.
+//
+// This assumes that the buffer has a compatible layout, meaning the buffer can
+// contain the image data as in memory.
+//
+// This is a helper for implementing vo_driver.get_image.
+//
+// vo: guess what
+// user_opaque: first parameter for the following callbacks
+// get_buffer: allocate and return persistently GPU mapped memory of the
+// given size. returns NULL on failure.
+// free_buffer: release memory returned by get_buffer (the ptr is the return
+// value of a previous get_buffer call)
+//
+// Note that get_buffer/free_buffer can be freely interleaved - even if the
+// pool is reallocated due to a parameter change, it could keep some buffers
+// for a while.
+//
+// We leave it undefined whether the pool will use a single buffer allocation
+// for exactly 1 or possibly multiple images, or whether it will reuse buffers
+// for images with different formats.
+struct vo_synced_pool *vo_synced_pool_create(struct vo *vo, void *user_opaque,
+ void *(*get_buffer)(void *user_opaque, size_t size),
+ void (*free_buffer)(void *user_opaque, void *ptr))
+{
+ struct vo_synced_pool *p = talloc_ptrtype(NULL, p);
+ talloc_set_destructor(p, synced_pool_free);
+ *p = (struct vo_synced_pool){
+ .vo = vo,
+ .user_opaque = user_opaque,
+ .get_buffer = get_buffer,
+ .free_buffer = free_buffer,
+ .pool = mp_image_pool_new(64),
+ };
+
+ pthread_mutex_init(&p->lock, NULL);
+
+ return p;
+}
+
+struct cmd_params {
+ struct vo_synced_pool *p;
+ size_t size;
+ void *ptr;
+};
+
+static void vo_thread_alloc(void *ptr)
+{
+ struct cmd_params *params = ptr;
+ params->ptr = params->p->get_buffer(params->p->user_opaque, params->size);
+}
+
+static void vo_thread_free(void *ptr)
+{
+ struct cmd_params *params = ptr;
+ params->p->free_buffer(params->p->user_opaque, params->ptr);
+}
+
+static void free_dr_image(void *opaque, uint8_t *data)
+{
+ struct vo_synced_pool *p = opaque;
+
+ struct cmd_params params = {
+ .p = p,
+ .ptr = data,
+ };
+
+ // The image could be unreffed even on the VO thread. In practice, this
+ // matters most on VO destruction.
+ if (pthread_equal(p->vo->in->thread, pthread_self())) {
+ vo_thread_free(&params);
+ } else {
+ mp_dispatch_run(p->vo->in->dispatch, vo_thread_free, &params);
+ }
+}
+
+// Fully thread-safe allocator function. The only exception is that you must
+// not call this from the VO thread - doing so would deadlock it. If no image
+// is available, the VO will be locked, and the provided get_buffer callback is
+// invoked. If the format changes, this might do expensive pool reallocation.
+struct mp_image *vo_synced_pool_get_image(struct vo_synced_pool *p, int imgfmt,
+ int w, int h, int stride_align)
+{
+ struct mp_image *res = NULL;
+ pthread_mutex_lock(&p->lock);
+
+ // (For simplicity, we realloc on any parameter change, instead of trying
+ // to be clever.)
+ if (stride_align != p->pool_align || w != p->pool_w || h != p->pool_h ||
+ imgfmt != p->pool_imgfmt)
+ {
+ mp_image_pool_clear(p->pool);
+ p->pool_imgfmt = imgfmt;
+ p->pool_w = w;
+ p->pool_h = h;
+ p->pool_align = stride_align;
+ }
+
+ res = mp_image_pool_get_no_alloc(p->pool, imgfmt, w, h);
+ if (res)
+ goto done;
+
+ // Allocate a new picture.
+
+ int stride[MP_MAX_PLANES];
+ int plane_size[MP_MAX_PLANES];
+ int size = mp_image_layout(imgfmt, w, h, stride_align, stride, plane_size);
+ if (size < 0)
+ goto done;
+ int total_size = size + stride_align; // pad to make sure we can realign it
+
+ struct cmd_params params = {
+ .p = p,
+ .size = total_size,
+ };
+ mp_dispatch_run(p->vo->in->dispatch, vo_thread_alloc, &params);
+ void *ptr = params.ptr;
+ if (!ptr)
+ goto done;
+
+ uint8_t *cur = (uint8_t *)(void *)MP_ALIGN_UP((uintptr_t)ptr, stride_align);
+
+ res = mp_image_new_dummy_ref(NULL);
+ mp_image_setfmt(res, imgfmt);
+ mp_image_set_size(res, w, h);
+
+ for (int n = 0; n < MP_MAX_PLANES; n++) {
+ res->planes[n] = plane_size[n] ? cur : NULL;
+ res->stride[n] = stride[n];
+ cur += plane_size[n];
+ }
+
+ res->bufs[0] = av_buffer_create(ptr, size, free_dr_image, p, 0);
+ if (!res->bufs[0])
+ abort(); // tiny malloc OOM
+
+ // Now make the mp_image part of the pool. This requires doing magic to the
+ // image, so just add it to the pool and get it back to avoid dealing with
+ // magic ourselves.
+ mp_image_pool_add(p->pool, res);
+ res = mp_image_pool_get_no_alloc(p->pool, imgfmt, w, h);
+
+done:
+ pthread_mutex_unlock(&p->lock);
+ return res;
+}
diff --git a/video/out/vo.h b/video/out/vo.h
index 6dce8f6c2f..47f9b3551b 100644
--- a/video/out/vo.h
+++ b/video/out/vo.h
@@ -280,6 +280,39 @@ struct vo_driver {
int (*control)(struct vo *vo, uint32_t request, void *data);
/*
+ * lavc callback for direct rendering
+ *
+ * Optional, must be fully thread-safe and reentrant. This function must be
+ * fast, and you should back it by a pool allocator. It is recommended that
+ * the pool is flushed if the parameters change (meaning all free images are
+ * destroyed, and the remaining images will be freed as soon as they are
+ * unreffed).
+ *
+ * It is guaranteed that the last reference to an image is destroyed before
+ * ->uninit is called (except it's not - libmpv screenshots can hold the
+ * reference longer, fuck).
+ *
+ * If this callback is implemented, it is recommended to make it simply call
+ * vo_synced_pool_get_image(), which takes care of all the pool management
+ * and synchronization.
+ *
+ * The allocated image - or a part of it, can be passed to draw_frame(). The
+ * point of this mechanism is that the decoder directly renders to GPU
+ * staging memory, to avoid a memcpy on frame uploadl. But this is not a
+ * guarantee. A filter could change the data pointers or return a newly
+ * allocated image. It's even possible that only 1 plane uses the buffer
+ * allocated by the get_image function. The VO has to check for this.
+ *
+ * stride_align is always a value >=1 that is a power of 2. The stride
+ * values of the returned image must be divisible by this value.
+ *
+ * returns: an allocated, refcounted image; if NULL is returned, the caller
+ * will silently fallback to a default allocator
+ */
+ struct mp_image *(*get_image)(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.
@@ -410,6 +443,8 @@ double vo_get_estimated_vsync_jitter(struct vo *vo);
double vo_get_display_fps(struct vo *vo);
double vo_get_delay(struct vo *vo);
void vo_discard_timing_info(struct vo *vo);
+struct mp_image *vo_get_image(struct vo *vo, int imgfmt, int w, int h,
+ int stride_align);
void vo_wakeup(struct vo *vo);
void vo_wait_default(struct vo *vo, int64_t until_time);
@@ -426,4 +461,11 @@ void vo_get_src_dst_rects(struct vo *vo, struct mp_rect *out_src,
struct vo_frame *vo_frame_ref(struct vo_frame *frame);
+struct vo_synced_pool;
+struct vo_synced_pool *vo_synced_pool_create(struct vo *vo, void *user_opaque,
+ void *(*get_buffer)(void *user_opaque, size_t size),
+ void (*free_buffer)(void *user_opaque, void *ptr));
+struct mp_image *vo_synced_pool_get_image(struct vo_synced_pool *p, int imgfmt,
+ int w, int h, int stride_align);
+
#endif /* MPLAYER_VIDEO_OUT_H */
diff --git a/video/out/vo_opengl.c b/video/out/vo_opengl.c
index f5b0bd37c4..6a1e5fba92 100644
--- a/video/out/vo_opengl.c
+++ b/video/out/vo_opengl.c
@@ -83,6 +83,8 @@ struct gl_priv {
GLsync vsync_fences[NUM_VSYNC_FENCES];
int num_vsync_fences;
+
+ struct vo_synced_pool *dr_pool;
};
static void resize(struct gl_priv *p)
@@ -343,10 +345,35 @@ static void wait_events(struct vo *vo, int64_t until_time_us)
}
}
+static void *dr_alloc_buffer(void *user_opaque, size_t size)
+{
+ struct gl_video *renderer = user_opaque;
+ return gl_video_dr_alloc_buffer(renderer, size);
+}
+
+static void dr_free_buffer(void *user_opaque, void *ptr)
+{
+ struct gl_video *renderer = user_opaque;
+ gl_video_dr_free_buffer(renderer, ptr);
+}
+
+static struct mp_image *get_image(struct vo *vo, int imgfmt, int w, int h,
+ int stride_align)
+{
+ struct gl_priv *p = vo->priv;
+ return vo_synced_pool_get_image(p->dr_pool, imgfmt, w, h, stride_align);
+}
+
static void uninit(struct vo *vo)
{
struct gl_priv *p = vo->priv;
+ // Hack to make it unref all images.
+ gl_video_set_hwdec(p->renderer, NULL);
+
+ // Will free all still pooled DR buffers.
+ talloc_free(p->dr_pool);
+
gl_video_uninit(p->renderer);
gl_hwdec_uninit(p->hwdec);
if (vo->hwdec_devs) {
@@ -410,6 +437,9 @@ static int preinit(struct vo *vo)
vo->hwdec_devs, vo->opts->gl_hwdec_interop);
gl_video_set_hwdec(p->renderer, p->hwdec);
+ p->dr_pool = vo_synced_pool_create(vo, p->renderer, dr_alloc_buffer,
+ dr_free_buffer);
+
return 0;
err_out:
@@ -427,6 +457,7 @@ const struct vo_driver video_out_opengl = {
.query_format = query_format,
.reconfig = reconfig,
.control = control,
+ .get_image = get_image,
.draw_frame = draw_frame,
.flip_page = flip_page,
.wait_events = wait_events,