4691306abf
Add drm_test Signed-off-by: arvin.zhu <arvin.zhu@starfivetech.com>
777 lines
23 KiB
C
Executable File
777 lines
23 KiB
C
Executable File
/*
|
|
* modeset - DRM Double-Buffered VSync'ed Modesetting Example
|
|
*
|
|
* Written 2012 by David Rheinsberg <david.rheinsberg@gmail.com>
|
|
* Dedicated to the Public Domain.
|
|
*/
|
|
|
|
/*
|
|
* DRM Double-Buffered VSync'ed Modesetting Howto
|
|
* This example extends modeset-double-buffered.c and introduces page-flips
|
|
* synced with vertical-blanks (vsync'ed). A vertical-blank is the time-period
|
|
* when a display-controller pauses from scanning out the framebuffer. After the
|
|
* vertical-blank is over, the framebuffer is again scanned out line by line and
|
|
* followed again by a vertical-blank.
|
|
*
|
|
* Vertical-blanks are important when changing a framebuffer. We already
|
|
* introduced double-buffering, so this example shows how we can flip the
|
|
* buffers during a vertical blank and _not_ during the scanout period.
|
|
*
|
|
* This example assumes that you are familiar with modeset-double-buffered. Only
|
|
* the differences between both files are highlighted here.
|
|
*/
|
|
|
|
#define _GNU_SOURCE
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include <stdbool.h>
|
|
#include <stdint.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <sys/mman.h>
|
|
#include <time.h>
|
|
#include <unistd.h>
|
|
#include <xf86drm.h>
|
|
#include <xf86drmMode.h>
|
|
|
|
#define DRM_MODULE_NAME "starfive"
|
|
#define DRM_BUSID NULL
|
|
|
|
struct modeset_buf;
|
|
struct modeset_dev;
|
|
static int modeset_find_crtc(int fd, drmModeRes *res, drmModeConnector *conn,
|
|
struct modeset_dev *dev);
|
|
static int modeset_create_fb(int fd, struct modeset_buf *buf);
|
|
static void modeset_destroy_fb(int fd, struct modeset_buf *buf);
|
|
static int modeset_setup_dev(int fd, drmModeRes *res, drmModeConnector *conn,
|
|
struct modeset_dev *dev);
|
|
static int modeset_open(int *out, const char *node);
|
|
static int modeset_prepare(int fd);
|
|
static void modeset_draw(int fd);
|
|
static void modeset_draw_dev(int fd, struct modeset_dev *dev);
|
|
static void modeset_cleanup(int fd);
|
|
|
|
/*
|
|
* modeset_open() stays the same.
|
|
*/
|
|
|
|
static int modeset_open(int *out, const char *node)
|
|
{
|
|
int fd, ret;
|
|
uint64_t has_dumb;
|
|
|
|
fd = drmOpen(DRM_MODULE_NAME, DRM_BUSID);
|
|
if (fd < 0) {
|
|
ret = -errno;
|
|
fprintf(stderr, "cannot open '%s': %m\n", node);
|
|
return ret;
|
|
}
|
|
|
|
if (drmGetCap(fd, DRM_CAP_DUMB_BUFFER, &has_dumb) < 0 ||
|
|
!has_dumb) {
|
|
fprintf(stderr, "drm device '%s' does not support dumb buffers\n",
|
|
node);
|
|
close(fd);
|
|
return -EOPNOTSUPP;
|
|
}
|
|
|
|
*out = fd;
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* modeset_buf and modeset_dev stay mostly the same. But 6 new fields are added
|
|
* to modeset_dev: r, g, b, r_up, g_up, b_up. They are used to compute the
|
|
* current color that is drawn on this output device. You can ignore them as
|
|
* they aren't important for this example.
|
|
* The modeset-double-buffered.c example used exactly the same fields but as
|
|
* local variables in modeset_draw().
|
|
*
|
|
* The \pflip_pending variable is true when a page-flip is currently pending,
|
|
* that is, the kernel will flip buffers on the next vertical blank. The
|
|
* \cleanup variable is true if the device is currently cleaned up and no more
|
|
* pageflips should be scheduled. They are used to synchronize the cleanup
|
|
* routines.
|
|
*/
|
|
|
|
struct modeset_buf {
|
|
uint32_t width;
|
|
uint32_t height;
|
|
uint32_t stride;
|
|
uint32_t size;
|
|
uint32_t handle;
|
|
uint8_t *map;
|
|
uint32_t fb;
|
|
};
|
|
|
|
struct modeset_dev {
|
|
struct modeset_dev *next;
|
|
|
|
unsigned int front_buf;
|
|
struct modeset_buf bufs[2];
|
|
|
|
drmModeModeInfo mode;
|
|
uint32_t conn;
|
|
uint32_t crtc;
|
|
drmModeCrtc *saved_crtc;
|
|
|
|
bool pflip_pending;
|
|
bool cleanup;
|
|
|
|
uint8_t r, g, b;
|
|
bool r_up, g_up, b_up;
|
|
};
|
|
|
|
static struct modeset_dev *modeset_list = NULL;
|
|
|
|
/*
|
|
* modeset_prepare() stays the same.
|
|
*/
|
|
|
|
static int modeset_prepare(int fd)
|
|
{
|
|
drmModeRes *res;
|
|
drmModeConnector *conn;
|
|
unsigned int i;
|
|
struct modeset_dev *dev;
|
|
int ret;
|
|
|
|
/* retrieve resources */
|
|
res = drmModeGetResources(fd);
|
|
if (!res) {
|
|
fprintf(stderr, "cannot retrieve DRM resources (%d): %m\n",
|
|
errno);
|
|
return -errno;
|
|
}
|
|
|
|
/* iterate all connectors */
|
|
for (i = 0; i < res->count_connectors; ++i) {
|
|
/* get information for each connector */
|
|
conn = drmModeGetConnector(fd, res->connectors[i]);
|
|
if (!conn) {
|
|
fprintf(stderr, "cannot retrieve DRM connector %u:%u (%d): %m\n",
|
|
i, res->connectors[i], errno);
|
|
continue;
|
|
}
|
|
|
|
/* create a device structure */
|
|
dev = malloc(sizeof(*dev));
|
|
memset(dev, 0, sizeof(*dev));
|
|
dev->conn = conn->connector_id;
|
|
|
|
/* call helper function to prepare this connector */
|
|
ret = modeset_setup_dev(fd, res, conn, dev);
|
|
if (ret) {
|
|
if (ret != -ENOENT) {
|
|
errno = -ret;
|
|
fprintf(stderr, "cannot setup device for connector %u:%u (%d): %m\n",
|
|
i, res->connectors[i], errno);
|
|
}
|
|
free(dev);
|
|
drmModeFreeConnector(conn);
|
|
continue;
|
|
}
|
|
|
|
/* free connector data and link device into global list */
|
|
drmModeFreeConnector(conn);
|
|
dev->next = modeset_list;
|
|
modeset_list = dev;
|
|
}
|
|
|
|
/* free resources again */
|
|
drmModeFreeResources(res);
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* modeset_setup_dev() stays the same.
|
|
*/
|
|
|
|
static int modeset_setup_dev(int fd, drmModeRes *res, drmModeConnector *conn,
|
|
struct modeset_dev *dev)
|
|
{
|
|
int ret;
|
|
|
|
/* check if a monitor is connected */
|
|
if (conn->connection != DRM_MODE_CONNECTED) {
|
|
fprintf(stderr, "ignoring unused connector %u\n",
|
|
conn->connector_id);
|
|
return -ENOENT;
|
|
}
|
|
|
|
/* check if there is at least one valid mode */
|
|
if (conn->count_modes == 0) {
|
|
fprintf(stderr, "no valid mode for connector %u\n",
|
|
conn->connector_id);
|
|
return -EFAULT;
|
|
}
|
|
|
|
/* copy the mode information into our device structure and into both
|
|
* buffers */
|
|
memcpy(&dev->mode, &conn->modes[0], sizeof(dev->mode));
|
|
dev->bufs[0].width = conn->modes[0].hdisplay;
|
|
dev->bufs[0].height = conn->modes[0].vdisplay;
|
|
dev->bufs[1].width = conn->modes[0].hdisplay;
|
|
dev->bufs[1].height = conn->modes[0].vdisplay;
|
|
fprintf(stderr, "mode for connector %u is %ux%u\n",
|
|
conn->connector_id, dev->bufs[0].width, dev->bufs[0].height);
|
|
|
|
/* find a crtc for this connector */
|
|
ret = modeset_find_crtc(fd, res, conn, dev);
|
|
if (ret) {
|
|
fprintf(stderr, "no valid crtc for connector %u\n",
|
|
conn->connector_id);
|
|
return ret;
|
|
}
|
|
|
|
/* create framebuffer #1 for this CRTC */
|
|
ret = modeset_create_fb(fd, &dev->bufs[0]);
|
|
if (ret) {
|
|
fprintf(stderr, "cannot create framebuffer for connector %u\n",
|
|
conn->connector_id);
|
|
return ret;
|
|
}
|
|
|
|
/* create framebuffer #2 for this CRTC */
|
|
ret = modeset_create_fb(fd, &dev->bufs[1]);
|
|
if (ret) {
|
|
fprintf(stderr, "cannot create framebuffer for connector %u\n",
|
|
conn->connector_id);
|
|
modeset_destroy_fb(fd, &dev->bufs[0]);
|
|
return ret;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* modeset_find_crtc() stays the same.
|
|
*/
|
|
|
|
static int modeset_find_crtc(int fd, drmModeRes *res, drmModeConnector *conn,
|
|
struct modeset_dev *dev)
|
|
{
|
|
drmModeEncoder *enc;
|
|
unsigned int i, j;
|
|
int32_t crtc;
|
|
struct modeset_dev *iter;
|
|
|
|
/* first try the currently conected encoder+crtc */
|
|
if (conn->encoder_id)
|
|
enc = drmModeGetEncoder(fd, conn->encoder_id);
|
|
else
|
|
enc = NULL;
|
|
|
|
if (enc) {
|
|
if (enc->crtc_id) {
|
|
crtc = enc->crtc_id;
|
|
for (iter = modeset_list; iter; iter = iter->next) {
|
|
if (iter->crtc == crtc) {
|
|
crtc = -1;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (crtc >= 0) {
|
|
drmModeFreeEncoder(enc);
|
|
dev->crtc = crtc;
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
drmModeFreeEncoder(enc);
|
|
}
|
|
|
|
/* If the connector is not currently bound to an encoder or if the
|
|
* encoder+crtc is already used by another connector (actually unlikely
|
|
* but lets be safe), iterate all other available encoders to find a
|
|
* matching CRTC. */
|
|
for (i = 0; i < conn->count_encoders; ++i) {
|
|
enc = drmModeGetEncoder(fd, conn->encoders[i]);
|
|
if (!enc) {
|
|
fprintf(stderr, "cannot retrieve encoder %u:%u (%d): %m\n",
|
|
i, conn->encoders[i], errno);
|
|
continue;
|
|
}
|
|
|
|
/* iterate all global CRTCs */
|
|
for (j = 0; j < res->count_crtcs; ++j) {
|
|
/* check whether this CRTC works with the encoder */
|
|
if (!(enc->possible_crtcs & (1 << j)))
|
|
continue;
|
|
|
|
/* check that no other device already uses this CRTC */
|
|
crtc = res->crtcs[j];
|
|
for (iter = modeset_list; iter; iter = iter->next) {
|
|
if (iter->crtc == crtc) {
|
|
crtc = -1;
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* we have found a CRTC, so save it and return */
|
|
if (crtc >= 0) {
|
|
drmModeFreeEncoder(enc);
|
|
dev->crtc = crtc;
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
drmModeFreeEncoder(enc);
|
|
}
|
|
|
|
fprintf(stderr, "cannot find suitable CRTC for connector %u\n",
|
|
conn->connector_id);
|
|
return -ENOENT;
|
|
}
|
|
|
|
/*
|
|
* modeset_create_fb() stays the same.
|
|
*/
|
|
|
|
static int modeset_create_fb(int fd, struct modeset_buf *buf)
|
|
{
|
|
struct drm_mode_create_dumb creq;
|
|
struct drm_mode_destroy_dumb dreq;
|
|
struct drm_mode_map_dumb mreq;
|
|
int ret;
|
|
|
|
/* create dumb buffer */
|
|
memset(&creq, 0, sizeof(creq));
|
|
creq.width = buf->width;
|
|
creq.height = buf->height;
|
|
creq.bpp = 32;
|
|
ret = drmIoctl(fd, DRM_IOCTL_MODE_CREATE_DUMB, &creq);
|
|
if (ret < 0) {
|
|
fprintf(stderr, "cannot create dumb buffer (%d): %m\n",
|
|
errno);
|
|
return -errno;
|
|
}
|
|
buf->stride = creq.pitch;
|
|
buf->size = creq.size;
|
|
buf->handle = creq.handle;
|
|
|
|
/* create framebuffer object for the dumb-buffer */
|
|
ret = drmModeAddFB(fd, buf->width, buf->height, 32, 32, buf->stride,
|
|
buf->handle, &buf->fb);
|
|
if (ret) {
|
|
fprintf(stderr, "cannot create framebuffer (%d): %m\n",
|
|
errno);
|
|
ret = -errno;
|
|
goto err_destroy;
|
|
}
|
|
|
|
/* prepare buffer for memory mapping */
|
|
memset(&mreq, 0, sizeof(mreq));
|
|
mreq.handle = buf->handle;
|
|
ret = drmIoctl(fd, DRM_IOCTL_MODE_MAP_DUMB, &mreq);
|
|
if (ret) {
|
|
fprintf(stderr, "cannot map dumb buffer (%d): %m\n",
|
|
errno);
|
|
ret = -errno;
|
|
goto err_fb;
|
|
}
|
|
|
|
/* perform actual memory mapping */
|
|
buf->map = mmap(0, buf->size, PROT_READ | PROT_WRITE, MAP_SHARED,
|
|
fd, mreq.offset);
|
|
if (buf->map == MAP_FAILED) {
|
|
fprintf(stderr, "cannot mmap dumb buffer (%d): %m\n",
|
|
errno);
|
|
ret = -errno;
|
|
goto err_fb;
|
|
}
|
|
|
|
/* clear the framebuffer to 0 */
|
|
memset(buf->map, 0, buf->size);
|
|
|
|
return 0;
|
|
|
|
err_fb:
|
|
drmModeRmFB(fd, buf->fb);
|
|
err_destroy:
|
|
memset(&dreq, 0, sizeof(dreq));
|
|
dreq.handle = buf->handle;
|
|
drmIoctl(fd, DRM_IOCTL_MODE_DESTROY_DUMB, &dreq);
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* modeset_destroy_fb() stays the same.
|
|
*/
|
|
|
|
static void modeset_destroy_fb(int fd, struct modeset_buf *buf)
|
|
{
|
|
struct drm_mode_destroy_dumb dreq;
|
|
|
|
/* unmap buffer */
|
|
munmap(buf->map, buf->size);
|
|
|
|
/* delete framebuffer */
|
|
drmModeRmFB(fd, buf->fb);
|
|
|
|
/* delete dumb buffer */
|
|
memset(&dreq, 0, sizeof(dreq));
|
|
dreq.handle = buf->handle;
|
|
drmIoctl(fd, DRM_IOCTL_MODE_DESTROY_DUMB, &dreq);
|
|
}
|
|
|
|
/*
|
|
* main() also stays the same.
|
|
*/
|
|
|
|
int main(int argc, char **argv)
|
|
{
|
|
int ret, fd;
|
|
const char *card;
|
|
struct modeset_dev *iter;
|
|
struct modeset_buf *buf;
|
|
|
|
/* check which DRM device to open */
|
|
if (argc > 1)
|
|
card = argv[1];
|
|
else
|
|
card = "/dev/dri/card0";
|
|
|
|
fprintf(stderr, "using card '%s'\n", card);
|
|
|
|
/* open the DRM device */
|
|
ret = modeset_open(&fd, card);
|
|
if (ret)
|
|
goto out_return;
|
|
|
|
/* prepare all connectors and CRTCs */
|
|
ret = modeset_prepare(fd);
|
|
if (ret)
|
|
goto out_close;
|
|
|
|
/* perform actual modesetting on each found connector+CRTC */
|
|
for (iter = modeset_list; iter; iter = iter->next) {
|
|
iter->saved_crtc = drmModeGetCrtc(fd, iter->crtc);
|
|
buf = &iter->bufs[iter->front_buf];
|
|
ret = drmModeSetCrtc(fd, iter->crtc, buf->fb, 0, 0,
|
|
&iter->conn, 1, &iter->mode);
|
|
if (ret)
|
|
fprintf(stderr, "cannot set CRTC for connector %u (%d): %m\n",
|
|
iter->conn, errno);
|
|
}
|
|
|
|
/* draw some colors for 5seconds */
|
|
modeset_draw(fd);
|
|
|
|
/* cleanup everything */
|
|
modeset_cleanup(fd);
|
|
|
|
ret = 0;
|
|
|
|
out_close:
|
|
close(fd);
|
|
out_return:
|
|
if (ret) {
|
|
errno = -ret;
|
|
fprintf(stderr, "modeset failed with error %d: %m\n", errno);
|
|
} else {
|
|
fprintf(stderr, "exiting\n");
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* modeset_page_flip_event() is a callback-helper for modeset_draw() below.
|
|
* Please see modeset_draw() for more information.
|
|
*
|
|
* Note that this does nothing if the device is currently cleaned up. This
|
|
* allows to wait for outstanding page-flips during cleanup.
|
|
*/
|
|
|
|
static void modeset_page_flip_event(int fd, unsigned int frame,
|
|
unsigned int sec, unsigned int usec,
|
|
void *data)
|
|
{
|
|
struct modeset_dev *dev = data;
|
|
|
|
dev->pflip_pending = false;
|
|
if (!dev->cleanup)
|
|
modeset_draw_dev(fd, dev);
|
|
}
|
|
|
|
/*
|
|
* modeset_draw() changes heavily from all previous examples. The rendering has
|
|
* moved into another helper modeset_draw_dev() below, but modeset_draw() is now
|
|
* responsible of controlling when we have to redraw the outputs.
|
|
*
|
|
* So what we do: first redraw all outputs. We initialize the r/g/b/_up
|
|
* variables of each output first, although, you can safely ignore these.
|
|
* They're solely used to compute the next color. Then we call
|
|
* modeset_draw_dev() for each output. This function _always_ redraws the output
|
|
* and schedules a buffer-swap/flip for the next vertical-blank.
|
|
* We now have to wait for each vertical-blank to happen so we can draw the next
|
|
* frame. If a vblank happens, we simply call modeset_draw_dev() again and wait
|
|
* for the next vblank.
|
|
*
|
|
* Note: Different monitors can have different refresh-rates. That means, a
|
|
* vblank event is always assigned to a CRTC. Hence, we get different vblank
|
|
* events for each CRTC/modeset_dev that we use. This also means, that our
|
|
* framerate-controlled color-morphing is different on each monitor. If you want
|
|
* exactly the same frame on all monitors, we would have to share the
|
|
* color-values between all devices. However, for simplicity reasons, we don't
|
|
* do this here.
|
|
*
|
|
* So the last piece missing is how we get vblank events. libdrm provides
|
|
* drmWaitVBlank(), however, we aren't interested in _all_ vblanks, but only in
|
|
* the vblanks for our page-flips. We could use drmWaitVBlank() but there is a
|
|
* more convenient way: drmModePageFlip()
|
|
* drmModePageFlip() schedules a buffer-flip for the next vblank and then
|
|
* notifies us about it. It takes a CRTC-id, fb-id and an arbitrary
|
|
* data-pointer and then schedules the page-flip. This is fully asynchronous and
|
|
* returns immediately.
|
|
* When the page-flip happens, the DRM-fd will become readable and we can call
|
|
* drmHandleEvent(). This will read all vblank/page-flip events and call our
|
|
* modeset_page_flip_event() callback with the data-pointer that we passed to
|
|
* drmModePageFlip(). We simply call modeset_draw_dev() then so the next frame
|
|
* is rendered..
|
|
*
|
|
*
|
|
* So modeset_draw() is reponsible of waiting for the page-flip/vblank events
|
|
* for _all_ currently used output devices and schedule a redraw for them. We
|
|
* could easily do this in a while (1) { drmHandleEvent() } loop, however, this
|
|
* example shows how you can use the DRM-fd to integrate this into your own
|
|
* main-loop. If you aren't familiar with select(), poll() or epoll, please read
|
|
* it up somewhere else. There is plenty of documentation elsewhere on the
|
|
* internet.
|
|
*
|
|
* So what we do is adding the DRM-fd and the keyboard-input-fd (more precisely:
|
|
* the stdin FD) to a select-set and then we wait on this set. If the DRM-fd is
|
|
* readable, we call drmHandleEvents() to handle the page-flip events. If the
|
|
* input-fd is readable, we exit. So on any keyboard input we exit this loop
|
|
* (you need to press RETURN after each keyboard input to make this work).
|
|
*/
|
|
|
|
static void modeset_draw(int fd)
|
|
{
|
|
int ret;
|
|
fd_set fds;
|
|
time_t start, cur;
|
|
struct timeval v;
|
|
drmEventContext ev;
|
|
struct modeset_dev *iter;
|
|
|
|
/* init variables */
|
|
srand(time(&start));
|
|
FD_ZERO(&fds);
|
|
memset(&v, 0, sizeof(v));
|
|
memset(&ev, 0, sizeof(ev));
|
|
/* Set this to only the latest version you support. Version 2
|
|
* introduced the page_flip_handler, so we use that. */
|
|
ev.version = 2;
|
|
ev.page_flip_handler = modeset_page_flip_event;
|
|
|
|
/* redraw all outputs */
|
|
for (iter = modeset_list; iter; iter = iter->next) {
|
|
iter->r = rand() % 0xff;
|
|
iter->g = rand() % 0xff;
|
|
iter->b = rand() % 0xff;
|
|
iter->r_up = iter->g_up = iter->b_up = true;
|
|
|
|
modeset_draw_dev(fd, iter);
|
|
}
|
|
|
|
/* wait 5s for VBLANK or input events */
|
|
while (time(&cur) < start + 5) {
|
|
FD_SET(0, &fds);
|
|
FD_SET(fd, &fds);
|
|
v.tv_sec = start + 5 - cur;
|
|
|
|
ret = select(fd + 1, &fds, NULL, NULL, &v);
|
|
if (ret < 0) {
|
|
fprintf(stderr, "select() failed with %d: %m\n", errno);
|
|
break;
|
|
} else if (FD_ISSET(0, &fds)) {
|
|
fprintf(stderr, "exit due to user-input\n");
|
|
break;
|
|
} else if (FD_ISSET(fd, &fds)) {
|
|
drmHandleEvent(fd, &ev);
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* A short helper function to compute a changing color value. No need to
|
|
* understand it.
|
|
*/
|
|
|
|
static uint8_t next_color(bool *up, uint8_t cur, unsigned int mod)
|
|
{
|
|
uint8_t next;
|
|
|
|
next = cur + (*up ? 1 : -1) * (rand() % mod);
|
|
if ((*up && next < cur) || (!*up && next > cur)) {
|
|
*up = !*up;
|
|
next = cur;
|
|
}
|
|
|
|
return next;
|
|
}
|
|
|
|
/*
|
|
* modeset_draw_dev() is a new function that redraws the screen of a single
|
|
* output. It takes the DRM-fd and the output devices as arguments, redraws a
|
|
* new frame and schedules the page-flip for the next vsync.
|
|
*
|
|
* This function does the same as modeset_draw() did in the previous examples
|
|
* but only for a single output device now.
|
|
* After we are done rendering a frame, we have to swap the buffers. Instead of
|
|
* calling drmModeSetCrtc() as we did previously, we now want to schedule this
|
|
* page-flip for the next vertical-blank (vblank). We use drmModePageFlip() for
|
|
* this. It takes the CRTC-id and FB-id and will asynchronously swap the buffers
|
|
* when the next vblank occurs. Note that this is done by the kernel, so neither
|
|
* a thread is started nor any other magic is done in libdrm.
|
|
* The DRM_MODE_PAGE_FLIP_EVENT flag tells drmModePageFlip() to send us a
|
|
* page-flip event on the DRM-fd when the page-flip happened. The last argument
|
|
* is a data-pointer that is returned with this event.
|
|
* If we wouldn't pass this flag, we would not get notified when the page-flip
|
|
* happened.
|
|
*
|
|
* Note: If you called drmModePageFlip() and directly call it again, it will
|
|
* return EBUSY if the page-flip hasn't happened in between. So you almost
|
|
* always want to pass DRM_MODE_PAGE_FLIP_EVENT to get notified when the
|
|
* page-flip happens so you know when to render the next frame.
|
|
* If you scheduled a page-flip but call drmModeSetCrtc() before the next
|
|
* vblank, then the scheduled page-flip will become a no-op. However, you will
|
|
* still get notified when it happens and you still cannot call
|
|
* drmModePageFlip() again until it finished. So to sum it up: there is no way
|
|
* to effectively cancel a page-flip.
|
|
*
|
|
* If you wonder why drmModePageFlip() takes fewer arguments than
|
|
* drmModeSetCrtc(), then you should take into account, that drmModePageFlip()
|
|
* reuses the arguments from drmModeSetCrtc(). So things like connector-ids,
|
|
* x/y-offsets and so on have to be set via drmModeSetCrtc() first before you
|
|
* can use drmModePageFlip()! We do this in main() as all the previous examples
|
|
* did, too.
|
|
*/
|
|
|
|
static void modeset_draw_dev(int fd, struct modeset_dev *dev)
|
|
{
|
|
struct modeset_buf *buf;
|
|
unsigned int j, k, off;
|
|
int ret;
|
|
|
|
dev->r = next_color(&dev->r_up, dev->r, 20);
|
|
dev->g = next_color(&dev->g_up, dev->g, 10);
|
|
dev->b = next_color(&dev->b_up, dev->b, 5);
|
|
|
|
buf = &dev->bufs[dev->front_buf ^ 1];
|
|
for (j = 0; j < buf->height; ++j) {
|
|
for (k = 0; k < buf->width; ++k) {
|
|
off = buf->stride * j + k * 4;
|
|
*(uint32_t*)&buf->map[off] =
|
|
(dev->r << 16) | (dev->g << 8) | dev->b;
|
|
}
|
|
}
|
|
|
|
ret = drmModePageFlip(fd, dev->crtc, buf->fb,
|
|
DRM_MODE_PAGE_FLIP_EVENT, dev);
|
|
if (ret) {
|
|
fprintf(stderr, "cannot flip CRTC for connector %u (%d): %m\n",
|
|
dev->conn, errno);
|
|
} else {
|
|
dev->front_buf ^= 1;
|
|
dev->pflip_pending = true;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* modeset_cleanup() stays mostly the same. However, before resetting a CRTC to
|
|
* its previous state, we wait for any outstanding page-flip to complete. This
|
|
* isn't strictly neccessary, however, some DRM drivers are known to be buggy if
|
|
* we call drmModeSetCrtc() if there is a pending page-flip.
|
|
* Furthermore, we don't want any pending page-flips when our application exist.
|
|
* Because another application might pick up the DRM device and try to schedule
|
|
* their own page-flips which might then fail as long as our page-flip is
|
|
* pending.
|
|
* So lets be safe here and simply wait for any page-flips to complete. This is
|
|
* a blocking operation, but it's mostly just <16ms so we can ignore that.
|
|
*/
|
|
|
|
static void modeset_cleanup(int fd)
|
|
{
|
|
struct modeset_dev *iter;
|
|
drmEventContext ev;
|
|
int ret;
|
|
|
|
/* init variables */
|
|
memset(&ev, 0, sizeof(ev));
|
|
ev.version = DRM_EVENT_CONTEXT_VERSION;
|
|
ev.page_flip_handler = modeset_page_flip_event;
|
|
|
|
while (modeset_list) {
|
|
/* remove from global list */
|
|
iter = modeset_list;
|
|
modeset_list = iter->next;
|
|
|
|
/* if a pageflip is pending, wait for it to complete */
|
|
iter->cleanup = true;
|
|
fprintf(stderr, "wait for pending page-flip to complete...\n");
|
|
while (iter->pflip_pending) {
|
|
ret = drmHandleEvent(fd, &ev);
|
|
if (ret)
|
|
break;
|
|
}
|
|
|
|
/* restore saved CRTC configuration */
|
|
if (!iter->pflip_pending)
|
|
drmModeSetCrtc(fd,
|
|
iter->saved_crtc->crtc_id,
|
|
iter->saved_crtc->buffer_id,
|
|
iter->saved_crtc->x,
|
|
iter->saved_crtc->y,
|
|
&iter->conn,
|
|
1,
|
|
&iter->saved_crtc->mode);
|
|
drmModeFreeCrtc(iter->saved_crtc);
|
|
|
|
/* destroy framebuffers */
|
|
modeset_destroy_fb(fd, &iter->bufs[1]);
|
|
modeset_destroy_fb(fd, &iter->bufs[0]);
|
|
|
|
/* free allocated memory */
|
|
free(iter);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* This example shows how to make the kernel handle page-flips and how to wait
|
|
* for them in user-space. The select() example here should show you how you can
|
|
* integrate these loops into your own applications without the need for a
|
|
* separate modesetting thread.
|
|
*
|
|
* However, please note that vsync'ed double-buffering doesn't solve all
|
|
* problems. Imagine that you cannot render a frame fast enough to satisfy all
|
|
* vertical-blanks. In this situation, you don't want to wait after scheduling a
|
|
* page-flip until the vblank happens to draw the next frame. A solution for
|
|
* this is triple-buffering. It should be farily easy to extend this example to
|
|
* use triple-buffering, but feel free to contact me if you have any questions
|
|
* about it.
|
|
* Also note that the DRM kernel API is quite limited if you want to reschedule
|
|
* page-flips that haven't happened, yet. You cannot call drmModePageFlip()
|
|
* twice in a single scanout-period. The behavior of drmModeSetCrtc() while a
|
|
* page-flip is pending might also be unexpected.
|
|
* Unfortunately, there is no ultimate solution to all modesetting problems.
|
|
* This example shows the tools to do vsync'ed page-flips, however, it depends
|
|
* on your use-case how you have to implement it.
|
|
*
|
|
* If you want more code, I can recommend reading the source-code of:
|
|
* - plymouth (which uses dumb-buffers like this example; very easy to understand)
|
|
* - kmscon (which uses libuterm to do this)
|
|
* - wayland (very sophisticated DRM renderer; hard to understand fully as it
|
|
* uses more complicated techniques like DRM planes)
|
|
* - xserver (very hard to understand as it is split across many files/projects)
|
|
*
|
|
* Any feedback is welcome. Feel free to use this code freely for your own
|
|
* documentation or projects.
|
|
*
|
|
* - Hosted on http://github.com/dvdhrm/docs
|
|
* - Written by David Rheinsberg <david.rheinsberg@gmail.com>
|
|
*/
|