diff options
-rw-r--r-- | video/out/vo_drm.c | 254 |
1 files changed, 215 insertions, 39 deletions
diff --git a/video/out/vo_drm.c b/video/out/vo_drm.c index cad3b8106a..80757486c6 100644 --- a/video/out/vo_drm.c +++ b/video/out/vo_drm.c @@ -21,6 +21,7 @@ #include <stdbool.h> #include <sys/mman.h> #include <poll.h> +#include <time.h> #include <unistd.h> #include <libswscale/swscale.h> @@ -45,10 +46,9 @@ #define BYTES_PER_PIXEL 4 #define BITS_PER_PIXEL 32 #define USE_MASTER 0 -#define BUF_COUNT 2 -// Modulo that works correctly for negative numbers -#define MOD(a,b) ((((a)%(b))+(b))%(b)) +#define BUF_COUNT 4 +#define SWAPCHAIN_DEPTH 3 struct framebuffer { uint32_t width; @@ -60,6 +60,16 @@ struct framebuffer { uint32_t fb; }; +struct kms_frame { + struct framebuffer *fb; + struct drm_vsync_tuple vsync; +}; + +struct pflip_cb_closure { + struct priv *priv; + struct kms_frame *frame; +}; + struct priv { char *connector_spec; int mode_id; @@ -74,7 +84,13 @@ struct priv { struct framebuffer bufs[BUF_COUNT]; int front_buf; bool active; - bool pflip_happening; + bool waiting_for_flip; + bool still; + bool paused; + + struct kms_frame **fb_queue; + unsigned int fb_queue_len; + struct framebuffer *cur_fb; uint32_t depth; enum mp_imgfmt imgfmt; @@ -88,6 +104,9 @@ struct priv { struct mp_rect dst; struct mp_osd_res osd; struct mp_sws_context *sws; + + struct drm_vsync_tuple vsync; + struct vo_vsync_info vsync_info; }; static void fb_destroy(int fd, struct framebuffer *buf) @@ -158,7 +177,7 @@ err: return false; } -static bool fb_setup_double_buffering(struct vo *vo) +static bool fb_setup_buffers(struct vo *vo) { struct priv *p = vo->priv; @@ -178,14 +197,59 @@ static bool fb_setup_double_buffering(struct vo *vo) } } + p->cur_fb = &p->bufs[0]; + return true; } -static void page_flipped(int fd, unsigned int frame, unsigned int sec, +static void page_flipped(int fd, unsigned int msc, unsigned int sec, unsigned int usec, void *data) { - struct priv *p = data; - p->pflip_happening = false; + struct pflip_cb_closure *closure = data; + struct priv *p = closure->priv; + + // frame->vsync.ust is the timestamp of the pageflip that happened just before this flip was queued + // frame->vsync.msc is the sequence number of the pageflip that happened just before this flip was queued + // frame->vsync.sbc is the sequence number for the frame that was just flipped to screen + struct kms_frame *frame = closure->frame; + + const bool ready = + (p->vsync.msc != 0) && + (frame->vsync.ust != 0) && (frame->vsync.msc != 0); + + const uint64_t ust = (sec * 1000000LL) + usec; + + const unsigned int msc_since_last_flip = msc - p->vsync.msc; + + p->vsync.ust = ust; + p->vsync.msc = msc; + + if (ready) { + // Convert to mp_time + struct timespec ts; + if (clock_gettime(CLOCK_MONOTONIC, &ts)) + goto fail; + const uint64_t now_monotonic = ts.tv_sec * 1000000LL + ts.tv_nsec / 1000; + const uint64_t ust_mp_time = mp_time_us() - (now_monotonic - p->vsync.ust); + + const uint64_t ust_since_enqueue = p->vsync.ust - frame->vsync.ust; + const unsigned int msc_since_enqueue = p->vsync.msc - frame->vsync.msc; + const unsigned int sbc_since_enqueue = p->vsync.sbc - frame->vsync.sbc; + + p->vsync_info.vsync_duration = ust_since_enqueue / msc_since_enqueue; + p->vsync_info.skipped_vsyncs = msc_since_last_flip - 1; // Valid iff swap_buffers is called every vsync + p->vsync_info.last_queue_display_time = ust_mp_time + (sbc_since_enqueue * p->vsync_info.vsync_duration); + } + +fail: + p->waiting_for_flip = false; + talloc_free(closure); +} + +static void get_vsync(struct vo *vo, struct vo_vsync_info *info) +{ + struct priv *p = vo->priv; + *info = p->vsync_info; } static bool crtc_setup(struct vo *vo) @@ -195,7 +259,7 @@ static bool crtc_setup(struct vo *vo) return true; p->old_crtc = drmModeGetCrtc(p->kms->fd, p->kms->crtc_id); int ret = drmModeSetCrtc(p->kms->fd, p->kms->crtc_id, - p->bufs[MOD(p->front_buf - 1, BUF_COUNT)].fb, + p->cur_fb->fb, 0, 0, &p->kms->connector->connector_id, 1, &p->kms->mode.mode); p->active = true; @@ -211,7 +275,7 @@ static void crtc_release(struct vo *vo) p->active = false; // wait for current page flip - while (p->pflip_happening) { + while (p->waiting_for_flip) { int ret = drmHandleEvent(p->kms->fd, &p->ev); if (ret) { MP_ERR(vo, "drmHandleEvent failed: %i\n", ret); @@ -319,15 +383,48 @@ static int reconfig(struct vo *vo, struct mp_image_params *params) if (mp_sws_reinit(p->sws) < 0) return -1; + p->vsync_info.vsync_duration = 0; + p->vsync_info.skipped_vsyncs = -1; + p->vsync_info.last_queue_display_time = -1; + vo->want_redraw = true; return 0; } -static void draw_image(struct vo *vo, mp_image_t *mpi) +static void wait_on_flip(struct vo *vo) { struct priv *p = vo->priv; - if (p->active) { + // poll page flip finish event + while (p->waiting_for_flip) { + const int timeout_ms = 3000; + struct pollfd fds[1] = { { .events = POLLIN, .fd = p->kms->fd } }; + poll(fds, 1, timeout_ms); + if (fds[0].revents & POLLIN) { + const int ret = drmHandleEvent(p->kms->fd, &p->ev); + if (ret != 0) { + MP_ERR(vo, "drmHandleEvent failed: %i\n", ret); + return; + } + } + } +} + +static struct framebuffer *get_new_fb(struct vo *vo) +{ + struct priv *p = vo->priv; + + p->front_buf++; + p->front_buf %= BUF_COUNT; + + return &p->bufs[p->front_buf]; +} + +static void draw_image(struct vo *vo, mp_image_t *mpi, struct framebuffer *front_buf) +{ + struct priv *p = vo->priv; + + if (p->active && front_buf != NULL) { if (mpi) { struct mp_image src = *mpi; struct mp_rect src_rc = p->src; @@ -347,8 +444,6 @@ static void draw_image(struct vo *vo, mp_image_t *mpi) osd_draw_on_image(vo->osd, p->osd, 0, 0, p->cur_frame); } - struct framebuffer *front_buf = &p->bufs[p->front_buf]; - if (p->depth == 30) { // Pack GBRP10 image into XRGB2101010 for DRM const int w = p->cur_frame->w; @@ -386,35 +481,99 @@ static void draw_image(struct vo *vo, mp_image_t *mpi) } } -static void flip_page(struct vo *vo) +static void enqueue_frame(struct vo *vo, struct framebuffer *fb) +{ + struct priv *p = vo->priv; + + p->vsync.sbc++; + struct kms_frame *new_frame = talloc(p, struct kms_frame); + new_frame->fb = fb; + new_frame->vsync = p->vsync; + 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; - if (!p->active || p->pflip_happening) + + 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 priv *p = vo->priv; + + if (!p->active) return; - int ret = drmModePageFlip(p->kms->fd, p->kms->crtc_id, - p->bufs[p->front_buf].fb, - DRM_MODE_PAGE_FLIP_EVENT, p); + p->still = frame->still; + + // we redraw the entire image when OSD needs to be redrawn + const bool repeat = frame->repeat && !frame->redraw; + + struct framebuffer *fb = &p->bufs[p->front_buf]; + 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 kms_frame *frame) +{ + int ret = 0; + struct priv *p = vo->priv; + + p->cur_fb = frame->fb; + + // Alloc and fill the data struct for the page flip callback + struct pflip_cb_closure *data = talloc(p, struct pflip_cb_closure); + data->priv = p; + data->frame = frame; + + ret = drmModePageFlip(p->kms->fd, p->kms->crtc_id, + p->cur_fb->fb, + DRM_MODE_PAGE_FLIP_EVENT, data); if (ret) { MP_WARN(vo, "Failed to queue page flip: %s\n", mp_strerror(errno)); } else { - p->front_buf++; - p->front_buf %= BUF_COUNT; - p->pflip_happening = true; + p->waiting_for_flip = true; } - // poll page flip finish event - const int timeout_ms = 3000; - struct pollfd fds[1] = { - { .events = POLLIN, .fd = p->kms->fd }, - }; - poll(fds, 1, timeout_ms); - if (fds[0].revents & POLLIN) { - ret = drmHandleEvent(p->kms->fd, &p->ev); - if (ret != 0) { - MP_ERR(vo, "drmHandleEvent failed: %i\n", ret); - return; +} + +static void flip_page(struct vo *vo) +{ + struct priv *p = vo->priv; + const bool drain = p->paused || p->still; + + if (!p->active) + return; + + while (drain || p->fb_queue_len > SWAPCHAIN_DEPTH) { + if (p->waiting_for_flip) { + wait_on_flip(vo); + 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]); } } @@ -424,6 +583,10 @@ static void uninit(struct vo *vo) crtc_release(vo); + while (p->fb_queue_len > 0) { + swapchain_step(vo); + } + if (p->kms) { for (unsigned int i = 0; i < BUF_COUNT; i++) fb_destroy(p->kms->fd, &p->bufs[i]); @@ -471,8 +634,8 @@ static int preinit(struct vo *vo) p->imgfmt = IMGFMT_XRGB8888; } - if (!fb_setup_double_buffering(vo)) { - MP_ERR(vo, "Failed to set up double buffering.\n"); + if (!fb_setup_buffers(vo)) { + MP_ERR(vo, "Failed to set up buffers.\n"); goto err; } @@ -499,6 +662,10 @@ static int preinit(struct vo *vo) } mp_verbose(vo->log, "Monitor pixel aspect: %g\n", vo->monitor_par); + p->vsync_info.vsync_duration = 0; + p->vsync_info.skipped_vsyncs = -1; + p->vsync_info.last_queue_display_time = -1; + return 0; err: @@ -518,9 +685,6 @@ static int control(struct vo *vo, uint32_t request, void *arg) case VOCTRL_SCREENSHOT_WIN: *(struct mp_image**)arg = mp_image_new_copy(p->cur_frame); return VO_TRUE; - case VOCTRL_REDRAW_FRAME: - draw_image(vo, p->last_input); - return VO_TRUE; case VOCTRL_SET_PANSCAN: if (vo->config_ok) reconfig(vo, vo->params); @@ -532,6 +696,17 @@ static int control(struct vo *vo, uint32_t request, void *arg) *(double*)arg = fps; return VO_TRUE; } + case VOCTRL_PAUSE: + vo->want_redraw = true; + p->paused = true; + return VO_TRUE; + case VOCTRL_RESUME: + p->paused = false; + p->vsync_info.last_queue_display_time = -1; + p->vsync_info.skipped_vsyncs = 0; + p->vsync.ust = 0; + p->vsync.msc = 0; + return VO_TRUE; } return VO_NOTIMPL; } @@ -545,8 +720,9 @@ const struct vo_driver video_out_drm = { .query_format = query_format, .reconfig = reconfig, .control = control, - .draw_image = draw_image, + .draw_frame = draw_frame, .flip_page = flip_page, + .get_vsync = get_vsync, .uninit = uninit, .wait_events = wait_events, .wakeup = wakeup, |