mirror of
https://github.com/mpv-player/mpv
synced 2025-03-03 12:47:49 +00:00
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:
parent
dfe45f018e
commit
77980c8184
@ -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,
|
||||
|
Loading…
Reference in New Issue
Block a user