summaryrefslogtreecommitdiffstats
path: root/video/out/vo_drm.c
diff options
context:
space:
mode:
Diffstat (limited to 'video/out/vo_drm.c')
-rw-r--r--video/out/vo_drm.c458
1 files changed, 458 insertions, 0 deletions
diff --git a/video/out/vo_drm.c b/video/out/vo_drm.c
new file mode 100644
index 0000000..aae73f7
--- /dev/null
+++ b/video/out/vo_drm.c
@@ -0,0 +1,458 @@
+/*
+ * This file is part of mpv.
+ *
+ * mpv is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * mpv is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with mpv. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <assert.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdbool.h>
+#include <sys/mman.h>
+#include <poll.h>
+#include <unistd.h>
+
+#include <drm_fourcc.h>
+#include <libswscale/swscale.h>
+
+#include "common/msg.h"
+#include "drm_atomic.h"
+#include "drm_common.h"
+#include "osdep/timer.h"
+#include "sub/osd.h"
+#include "video/fmt-conversion.h"
+#include "video/mp_image.h"
+#include "video/out/present_sync.h"
+#include "video/sws_utils.h"
+#include "vo.h"
+
+#define IMGFMT_XRGB8888 IMGFMT_BGR0
+#if BYTE_ORDER == BIG_ENDIAN
+#define IMGFMT_XRGB2101010 pixfmt2imgfmt(AV_PIX_FMT_GBRP10BE)
+#else
+#define IMGFMT_XRGB2101010 pixfmt2imgfmt(AV_PIX_FMT_GBRP10LE)
+#endif
+
+#define BYTES_PER_PIXEL 4
+#define BITS_PER_PIXEL 32
+
+struct drm_frame {
+ struct framebuffer *fb;
+};
+
+struct priv {
+ struct drm_frame **fb_queue;
+ unsigned int fb_queue_len;
+
+ uint32_t drm_format;
+ enum mp_imgfmt imgfmt;
+
+ struct mp_image *last_input;
+ struct mp_image *cur_frame;
+ struct mp_image *cur_frame_cropped;
+ struct mp_rect src;
+ struct mp_rect dst;
+ struct mp_osd_res osd;
+ struct mp_sws_context *sws;
+
+ struct framebuffer **bufs;
+ int front_buf;
+ int buf_count;
+};
+
+static void destroy_framebuffer(int fd, struct framebuffer *fb)
+{
+ if (!fb)
+ return;
+
+ if (fb->map) {
+ munmap(fb->map, fb->size);
+ }
+ if (fb->id) {
+ drmModeRmFB(fd, fb->id);
+ }
+ if (fb->handle) {
+ struct drm_mode_destroy_dumb dreq = {
+ .handle = fb->handle,
+ };
+ drmIoctl(fd, DRM_IOCTL_MODE_DESTROY_DUMB, &dreq);
+ }
+}
+
+static struct framebuffer *setup_framebuffer(struct vo *vo)
+{
+ struct priv *p = vo->priv;
+ struct vo_drm_state *drm = vo->drm;
+
+ struct framebuffer *fb = talloc_zero(drm, struct framebuffer);
+ fb->width = drm->mode.mode.hdisplay;
+ fb->height = drm->mode.mode.vdisplay;
+ fb->fd = drm->fd;
+ fb->handle = 0;
+
+ // create dumb buffer
+ struct drm_mode_create_dumb creq = {
+ .width = fb->width,
+ .height = fb->height,
+ .bpp = BITS_PER_PIXEL,
+ };
+
+ if (drmIoctl(drm->fd, DRM_IOCTL_MODE_CREATE_DUMB, &creq) < 0) {
+ MP_ERR(vo, "Cannot create dumb buffer: %s\n", mp_strerror(errno));
+ goto err;
+ }
+
+ fb->stride = creq.pitch;
+ fb->size = creq.size;
+ fb->handle = creq.handle;
+
+ // select format
+ if (drm->opts->drm_format == DRM_OPTS_FORMAT_XRGB2101010) {
+ p->drm_format = DRM_FORMAT_XRGB2101010;
+ p->imgfmt = IMGFMT_XRGB2101010;
+ } else {
+ p->drm_format = DRM_FORMAT_XRGB8888;;
+ p->imgfmt = IMGFMT_XRGB8888;
+ }
+
+ // create framebuffer object for the dumb-buffer
+ int ret = drmModeAddFB2(fb->fd, fb->width, fb->height,
+ p->drm_format,
+ (uint32_t[4]){fb->handle, 0, 0, 0},
+ (uint32_t[4]){fb->stride, 0, 0, 0},
+ (uint32_t[4]){0, 0, 0, 0},
+ &fb->id, 0);
+ if (ret) {
+ MP_ERR(vo, "Cannot create framebuffer: %s\n", mp_strerror(errno));
+ goto err;
+ }
+
+ // prepare buffer for memory mapping
+ struct drm_mode_map_dumb mreq = {
+ .handle = fb->handle,
+ };
+ if (drmIoctl(drm->fd, DRM_IOCTL_MODE_MAP_DUMB, &mreq)) {
+ MP_ERR(vo, "Cannot map dumb buffer: %s\n", mp_strerror(errno));
+ goto err;
+ }
+
+ // perform actual memory mapping
+ fb->map = mmap(0, fb->size, PROT_READ | PROT_WRITE, MAP_SHARED,
+ drm->fd, mreq.offset);
+ if (fb->map == MAP_FAILED) {
+ MP_ERR(vo, "Cannot map dumb buffer: %s\n", mp_strerror(errno));
+ goto err;
+ }
+
+ memset(fb->map, 0, fb->size);
+ return fb;
+
+err:
+ destroy_framebuffer(drm->fd, fb);
+ return NULL;
+}
+
+static int reconfig(struct vo *vo, struct mp_image_params *params)
+{
+ struct priv *p = vo->priv;
+ struct vo_drm_state *drm = vo->drm;
+
+ vo->dwidth =drm->fb->width;
+ vo->dheight = drm->fb->height;
+ vo_get_src_dst_rects(vo, &p->src, &p->dst, &p->osd);
+
+ int w = p->dst.x1 - p->dst.x0;
+ int h = p->dst.y1 - p->dst.y0;
+
+ p->sws->src = *params;
+ p->sws->dst = (struct mp_image_params) {
+ .imgfmt = p->imgfmt,
+ .w = w,
+ .h = h,
+ .p_w = 1,
+ .p_h = 1,
+ };
+
+ talloc_free(p->cur_frame);
+ p->cur_frame = mp_image_alloc(p->imgfmt, drm->fb->width, drm->fb->height);
+ mp_image_params_guess_csp(&p->sws->dst);
+ mp_image_set_params(p->cur_frame, &p->sws->dst);
+ mp_image_set_size(p->cur_frame, drm->fb->width, drm->fb->height);
+
+ talloc_free(p->cur_frame_cropped);
+ p->cur_frame_cropped = mp_image_new_dummy_ref(p->cur_frame);
+ mp_image_crop_rc(p->cur_frame_cropped, p->dst);
+
+ talloc_free(p->last_input);
+ p->last_input = NULL;
+
+ if (mp_sws_reinit(p->sws) < 0)
+ return -1;
+
+ vo->want_redraw = true;
+ return 0;
+}
+
+static struct framebuffer *get_new_fb(struct vo *vo)
+{
+ struct priv *p = vo->priv;
+
+ p->front_buf++;
+ p->front_buf %= p->buf_count;
+
+ return p->bufs[p->front_buf];
+}
+
+static void draw_image(struct vo *vo, mp_image_t *mpi, struct framebuffer *buf)
+{
+ struct priv *p = vo->priv;
+ struct vo_drm_state *drm = vo->drm;
+
+ if (drm->active && buf != NULL) {
+ if (mpi) {
+ struct mp_image src = *mpi;
+ struct mp_rect src_rc = p->src;
+ src_rc.x0 = MP_ALIGN_DOWN(src_rc.x0, mpi->fmt.align_x);
+ src_rc.y0 = MP_ALIGN_DOWN(src_rc.y0, mpi->fmt.align_y);
+ mp_image_crop_rc(&src, src_rc);
+
+ mp_image_clear(p->cur_frame, 0, 0, p->cur_frame->w, p->dst.y0);
+ mp_image_clear(p->cur_frame, 0, p->dst.y1, p->cur_frame->w, p->cur_frame->h);
+ mp_image_clear(p->cur_frame, 0, p->dst.y0, p->dst.x0, p->dst.y1);
+ mp_image_clear(p->cur_frame, p->dst.x1, p->dst.y0, p->cur_frame->w, p->dst.y1);
+
+ mp_sws_scale(p->sws, p->cur_frame_cropped, &src);
+ osd_draw_on_image(vo->osd, p->osd, src.pts, 0, p->cur_frame);
+ } else {
+ mp_image_clear(p->cur_frame, 0, 0, p->cur_frame->w, p->cur_frame->h);
+ osd_draw_on_image(vo->osd, p->osd, 0, 0, p->cur_frame);
+ }
+
+ if (p->drm_format == DRM_FORMAT_XRGB2101010) {
+ // Pack GBRP10 image into XRGB2101010 for DRM
+ const int w = p->cur_frame->w;
+ const int h = p->cur_frame->h;
+
+ const int g_padding = p->cur_frame->stride[0]/sizeof(uint16_t) - w;
+ const int b_padding = p->cur_frame->stride[1]/sizeof(uint16_t) - w;
+ const int r_padding = p->cur_frame->stride[2]/sizeof(uint16_t) - w;
+ const int fbuf_padding = buf->stride/sizeof(uint32_t) - w;
+
+ uint16_t *g_ptr = (uint16_t*)p->cur_frame->planes[0];
+ uint16_t *b_ptr = (uint16_t*)p->cur_frame->planes[1];
+ uint16_t *r_ptr = (uint16_t*)p->cur_frame->planes[2];
+ uint32_t *fbuf_ptr = (uint32_t*)buf->map;
+ for (unsigned y = 0; y < h; ++y) {
+ for (unsigned x = 0; x < w; ++x) {
+ *fbuf_ptr++ = (*r_ptr++ << 20) | (*g_ptr++ << 10) | (*b_ptr++);
+ }
+ g_ptr += g_padding;
+ b_ptr += b_padding;
+ r_ptr += r_padding;
+ fbuf_ptr += fbuf_padding;
+ }
+ } else { // p->drm_format == DRM_FORMAT_XRGB8888
+ memcpy_pic(buf->map, p->cur_frame->planes[0],
+ p->cur_frame->w * BYTES_PER_PIXEL, p->cur_frame->h,
+ buf->stride,
+ p->cur_frame->stride[0]);
+ }
+ }
+
+ if (mpi != p->last_input) {
+ talloc_free(p->last_input);
+ p->last_input = mpi;
+ }
+}
+
+static void enqueue_frame(struct vo *vo, struct framebuffer *fb)
+{
+ struct priv *p = vo->priv;
+
+ struct drm_frame *new_frame = talloc(p, struct drm_frame);
+ new_frame->fb = fb;
+ MP_TARRAY_APPEND(p, p->fb_queue, p->fb_queue_len, new_frame);
+}
+
+static void dequeue_frame(struct vo *vo)
+{
+ struct priv *p = vo->priv;
+
+ talloc_free(p->fb_queue[0]);
+ MP_TARRAY_REMOVE_AT(p->fb_queue, p->fb_queue_len, 0);
+}
+
+static void swapchain_step(struct vo *vo)
+{
+ struct priv *p = vo->priv;
+
+ if (p->fb_queue_len > 0) {
+ dequeue_frame(vo);
+ }
+}
+
+static void draw_frame(struct vo *vo, struct vo_frame *frame)
+{
+ struct vo_drm_state *drm = vo->drm;
+ struct priv *p = vo->priv;
+
+ if (!drm->active)
+ return;
+
+ drm->still = frame->still;
+
+ // we redraw the entire image when OSD needs to be redrawn
+ struct framebuffer *fb = p->bufs[p->front_buf];
+ const bool repeat = frame->repeat && !frame->redraw;
+ if (!repeat) {
+ fb = get_new_fb(vo);
+ draw_image(vo, mp_image_new_ref(frame->current), fb);
+ }
+
+ enqueue_frame(vo, fb);
+}
+
+static void queue_flip(struct vo *vo, struct drm_frame *frame)
+{
+ struct vo_drm_state *drm = vo->drm;
+
+ drm->fb = frame->fb;
+
+ int ret = drmModePageFlip(drm->fd, drm->crtc_id,
+ drm->fb->id, DRM_MODE_PAGE_FLIP_EVENT, drm);
+ if (ret)
+ MP_WARN(vo, "Failed to queue page flip: %s\n", mp_strerror(errno));
+ drm->waiting_for_flip = !ret;
+}
+
+static void flip_page(struct vo *vo)
+{
+ struct priv *p = vo->priv;
+ struct vo_drm_state *drm = vo->drm;
+ const bool drain = drm->paused || drm->still;
+
+ if (!drm->active)
+ return;
+
+ while (drain || p->fb_queue_len > vo->opts->swapchain_depth) {
+ if (drm->waiting_for_flip) {
+ vo_drm_wait_on_flip(vo->drm);
+ swapchain_step(vo);
+ }
+ if (p->fb_queue_len <= 1)
+ break;
+ if (!p->fb_queue[1] || !p->fb_queue[1]->fb) {
+ MP_ERR(vo, "Hole in swapchain?\n");
+ swapchain_step(vo);
+ continue;
+ }
+ queue_flip(vo, p->fb_queue[1]);
+ }
+}
+
+static void get_vsync(struct vo *vo, struct vo_vsync_info *info)
+{
+ struct vo_drm_state *drm = vo->drm;
+ present_sync_get_info(drm->present, info);
+}
+
+static void uninit(struct vo *vo)
+{
+ struct priv *p = vo->priv;
+
+ vo_drm_uninit(vo);
+
+ while (p->fb_queue_len > 0) {
+ swapchain_step(vo);
+ }
+
+ talloc_free(p->last_input);
+ talloc_free(p->cur_frame);
+ talloc_free(p->cur_frame_cropped);
+}
+
+static int preinit(struct vo *vo)
+{
+ struct priv *p = vo->priv;
+
+ if (!vo_drm_init(vo))
+ goto err;
+
+ struct vo_drm_state *drm = vo->drm;
+ p->buf_count = vo->opts->swapchain_depth + 1;
+ p->bufs = talloc_zero_array(p, struct framebuffer *, p->buf_count);
+
+ p->front_buf = 0;
+ for (int i = 0; i < p->buf_count; i++) {
+ p->bufs[i] = setup_framebuffer(vo);
+ if (!p->bufs[i])
+ goto err;
+ }
+ drm->fb = p->bufs[0];
+
+ vo->drm->width = vo->drm->fb->width;
+ vo->drm->height = vo->drm->fb->height;
+
+ if (!vo_drm_acquire_crtc(vo->drm)) {
+ MP_ERR(vo, "Failed to set CRTC for connector %u: %s\n",
+ vo->drm->connector->connector_id, mp_strerror(errno));
+ goto err;
+ }
+
+ vo_drm_set_monitor_par(vo);
+ p->sws = mp_sws_alloc(vo);
+ p->sws->log = vo->log;
+ mp_sws_enable_cmdline_opts(p->sws, vo->global);
+ return 0;
+
+err:
+ uninit(vo);
+ return -1;
+}
+
+static int query_format(struct vo *vo, int format)
+{
+ return sws_isSupportedInput(imgfmt2pixfmt(format));
+}
+
+static int control(struct vo *vo, uint32_t request, void *arg)
+{
+ switch (request) {
+ case VOCTRL_SET_PANSCAN:
+ if (vo->config_ok)
+ reconfig(vo, vo->params);
+ return VO_TRUE;
+ }
+
+ int events = 0;
+ int ret = vo_drm_control(vo, &events, request, arg);
+ vo_event(vo, events);
+ return ret;
+}
+
+const struct vo_driver video_out_drm = {
+ .name = "drm",
+ .description = "Direct Rendering Manager (software scaling)",
+ .preinit = preinit,
+ .query_format = query_format,
+ .reconfig = reconfig,
+ .control = control,
+ .draw_frame = draw_frame,
+ .flip_page = flip_page,
+ .get_vsync = get_vsync,
+ .uninit = uninit,
+ .wait_events = vo_drm_wait_events,
+ .wakeup = vo_drm_wakeup,
+ .priv_size = sizeof(struct priv),
+};