123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644 |
- /*
- * modeset - DRM Double-Buffered Modesetting Example
- *
- * Written 2012 by David Rheinsberg <david.rheinsberg@gmail.com>
- * Dedicated to the Public Domain.
- */
- /*
- * DRM Double-Buffered Modesetting Howto
- * This example extends the modeset.c howto and introduces double-buffering.
- * When drawing a new frame into a framebuffer, we should always draw into an
- * unused buffer and not into the front buffer. If we draw into the front
- * buffer, we might have drawn half the frame when the display-controller starts
- * scanning out the next frame. Hence, we see flickering on the screen.
- * The technique to avoid this is called double-buffering. We have two
- * framebuffers, the front buffer which is currently used for scanout and a
- * back-buffer that is used for drawing operations. When a frame is done, we
- * simply swap both buffers.
- * Swapping does not mean copying data, instead, only the pointers to the
- * buffers are swapped.
- *
- * Please read modeset.c before reading this file as most of the functions stay
- * the same. Only the differences are highlighted here.
- * Also note that triple-buffering or any other number of buffers can be easily
- * implemented by following the scheme here. However, in this example we limit
- * the number of buffers to 2 so it is easier to follow.
- */
- #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>
- 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_cleanup(int fd);
- /*
- * modeset_open() stays the same as before.
- */
- static int modeset_open(int *out, const char *node)
- {
- int fd, ret;
- uint64_t has_dumb;
- fd = open(node, O_RDWR | O_CLOEXEC);
- 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;
- }
- /*
- * Previously, we used the modeset_dev objects to hold buffer informations, too.
- * Technically, we could have split them but avoided this to make the
- * example simpler.
- * However, in this example we need 2 buffers. One back buffer and one front
- * buffer. So we introduce a new structure modeset_buf which contains everything
- * related to a single buffer. Each device now gets an array of two of these
- * buffers.
- * Each buffer consists of width, height, stride, size, handle, map and fb-id.
- * They have the same meaning as before.
- *
- * Each device also gets a new integer field: front_buf. This field contains the
- * index of the buffer that is currently used as front buffer / scanout buffer.
- * In our example it can be 0 or 1. We flip it by using XOR:
- * dev->front_buf ^= dev->front_buf
- *
- * Everything else stays the same.
- */
- 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;
- };
- 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() sets up all resources for a single device. It mostly
- * stays the same, but one thing changes: We allocate two framebuffers instead
- * of one. That is, we call modeset_create_fb() twice.
- * We also copy the width/height information into both framebuffers so
- * modeset_create_fb() can use them without requiring a pointer to modeset_dev.
- */
- 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() is mostly the same as before. Buf instead of writing the
- * fields of a modeset_dev, we now require a buffer pointer passed as @buf.
- * Please note that buf->width and buf->height are initialized by
- * modeset_setup_dev() so we can use them here.
- */
- 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() is a new function. It does exactly the reverse of
- * modeset_create_fb() and destroys a single framebuffer. The modeset.c example
- * used to do this directly in modeset_cleanup().
- * We simply unmap the buffer, remove the drm-FB and destroy the memory buffer.
- */
- 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 almost exactly the same as before. We only need to change
- * the way that we initially set the CRTCs. Instead of using the buffer
- * information from modeset_dev, we now use dev->bufs[iter->front_buf] to get
- * the current front-buffer and use this framebuffer for drmModeSetCrtc().
- */
- 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;
- }
- /*
- * 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() is the place where things change. The render-logic is the same
- * and we still draw a solid-color on the whole screen. However, we now have two
- * buffers and need to flip between them.
- *
- * So before drawing into a framebuffer, we need to find the back-buffer.
- * Remember, dev->font_buf is the index of the front buffer, so
- * dev->front_buf ^ 1 is the index of the back buffer. We simply use
- * dev->bufs[dev->front_buf ^ 1] to get the back-buffer and draw into it.
- *
- * After we finished drawing, we need to flip the buffers. We do this with the
- * same call as we initially set the CRTC: drmModeSetCrtc(). However, we now
- * pass the back-buffer as new framebuffer as we want to flip them.
- * The only thing left to do is to change the dev->front_buf index to point to
- * the new back-buffer (which was previously the front buffer).
- * We then sleep for a short time period and start drawing again.
- *
- * If you run this example, you will notice that there is almost no flickering,
- * anymore. The buffers are now swapped as a whole so each new frame shows
- * always the whole new image. If you look carefully, you will notice that the
- * modeset.c example showed many screen corruptions during redraw-cycles.
- *
- * However, this example is still not perfect. Imagine the display-controller is
- * currently scanning out a new image and we call drmModeSetCrtc()
- * simultaneously. It will then have the same effect as if we used a single
- * buffer and we get some tearing. But, the chance that this happens is a lot
- * less likely as with a single-buffer. This is because there is a long period
- * between each frame called vertical-blank where the display-controller does
- * not perform a scanout. If we swap the buffers in this period, we have the
- * guarantee that there will be no tearing. See the modeset-vsync.c example if
- * you want to know how you can guarantee that the swap takes place at a
- * vertical-sync.
- */
- static void modeset_draw(int fd)
- {
- uint8_t r, g, b;
- bool r_up, g_up, b_up;
- unsigned int i, j, k, off;
- struct modeset_dev *iter;
- struct modeset_buf *buf;
- int ret;
- srand(time(NULL));
- r = rand() % 0xff;
- g = rand() % 0xff;
- b = rand() % 0xff;
- r_up = g_up = b_up = true;
- for (i = 0; i < 50; ++i) {
- r = next_color(&r_up, r, 20);
- g = next_color(&g_up, g, 10);
- b = next_color(&b_up, b, 5);
- for (iter = modeset_list; iter; iter = iter->next) {
- buf = &iter->bufs[iter->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] =
- (r << 16) | (g << 8) | b;
- }
- }
- ret = drmModeSetCrtc(fd, iter->crtc, buf->fb, 0, 0,
- &iter->conn, 1, &iter->mode);
- if (ret)
- fprintf(stderr, "cannot flip CRTC for connector %u (%d): %m\n",
- iter->conn, errno);
- else
- iter->front_buf ^= 1;
- }
- usleep(100000);
- }
- }
- /*
- * modeset_cleanup() stays the same as before. But it now calls
- * modeset_destroy_fb() instead of accessing the framebuffers directly.
- */
- static void modeset_cleanup(int fd)
- {
- struct modeset_dev *iter;
- while (modeset_list) {
- /* remove from global list */
- iter = modeset_list;
- modeset_list = iter->next;
- /* restore saved CRTC configuration */
- 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 was a very short extension to the basic modesetting example that shows
- * how double-buffering is implemented. Double-buffering is the de-facto
- * standard in any graphics application so any other example will be based on
- * this. It is important to understand the ideas behind it as the code is pretty
- * easy and short compared to modeset.c.
- *
- * Double-buffering doesn't solve all problems. Vsync'ed page-flips solve most
- * of the problems that still occur, but has problems on it's own (see
- * modeset-vsync.c for a discussion).
- *
- * 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>
- */
|