vo_drm: Implement N-buffering and presentation feedback

Swapchain depth currently hard-coded to 3 (4 buffers).

As we now avoid redrawing on repeat frames (we simply requeue the same fb
again), this should give a nice performance boost when playing videos with a
lower FPS than the display FPS in video-sync=display-resample mode.

Presentation feedback has also been implemented to help counter the
significant amounts of jitter we would otherwise be seeing.
This commit is contained in:
Anton Kindestam 2019-04-11 23:26:06 +02:00 committed by Jan Ekström
parent dfe45f018e
commit 77980c8184
1 changed files with 215 additions and 39 deletions

View File

@ -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;
if (!p->active || p->pflip_happening)
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;
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,