├── .gitignore └── drm-howto ├── Android.bp ├── LICENSE ├── Makefile ├── modeset-double-buffered.c ├── modeset-vsync.c └── modeset.c /.gitignore: -------------------------------------------------------------------------------- 1 | drm-howto/modeset 2 | drm-howto/modeset-double-buffered 3 | drm-howto/modeset-vsync 4 | -------------------------------------------------------------------------------- /drm-howto/Android.bp: -------------------------------------------------------------------------------- 1 | cc_test { 2 | srcs: [ 3 | "modeset.c", 4 | ], 5 | name: "modeset", 6 | defaults: [ 7 | "libdrm_defaults", 8 | ], 9 | 10 | shared_libs: ["libdrm"], 11 | static_libs: ["libdrm_util"], 12 | } 13 | 14 | cc_test { 15 | srcs: [ 16 | "modeset-double-buffered.c", 17 | ], 18 | name: "modeset-double-buffered", 19 | defaults: [ 20 | "libdrm_defaults", 21 | ], 22 | 23 | shared_libs: ["libdrm"], 24 | static_libs: ["libdrm_util"], 25 | } 26 | 27 | cc_test { 28 | srcs: [ 29 | "modeset-vsync.c", 30 | ], 31 | name: "modeset-vsync", 32 | defaults: [ 33 | "libdrm_defaults", 34 | ], 35 | 36 | shared_libs: ["libdrm"], 37 | static_libs: ["libdrm_util"], 38 | } 39 | 40 | 41 | -------------------------------------------------------------------------------- /drm-howto/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2012-2017 David Herrmann 2 | 3 | Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. 4 | 5 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 6 | -------------------------------------------------------------------------------- /drm-howto/Makefile: -------------------------------------------------------------------------------- 1 | FLAGS=`pkg-config --cflags --libs libdrm` 2 | FLAGS+=-Wall -O0 -g 3 | FLAGS+=-D_FILE_OFFSET_BITS=64 4 | 5 | all: 6 | gcc -o modeset modeset.c $(FLAGS) 7 | gcc -o modeset-double-buffered modeset-double-buffered.c $(FLAGS) 8 | gcc -o modeset-vsync modeset-vsync.c $(FLAGS) 9 | -------------------------------------------------------------------------------- /drm-howto/modeset-double-buffered.c: -------------------------------------------------------------------------------- 1 | /* 2 | * modeset - DRM Double-Buffered Modesetting Example 3 | * 4 | * Written 2012 by David Herrmann 5 | * Dedicated to the Public Domain. 6 | */ 7 | 8 | /* 9 | * DRM Double-Buffered Modesetting Howto 10 | * This example extends the modeset.c howto and introduces double-buffering. 11 | * When drawing a new frame into a framebuffer, we should always draw into an 12 | * unused buffer and not into the front buffer. If we draw into the front 13 | * buffer, we might have drawn half the frame when the display-controller starts 14 | * scanning out the next frame. Hence, we see flickering on the screen. 15 | * The technique to avoid this is called double-buffering. We have two 16 | * framebuffers, the front buffer which is currently used for scanout and a 17 | * back-buffer that is used for drawing operations. When a frame is done, we 18 | * simply swap both buffers. 19 | * Swapping does not mean copying data, instead, only the pointers to the 20 | * buffers are swapped. 21 | * 22 | * Please read modeset.c before reading this file as most of the functions stay 23 | * the same. Only the differences are highlighted here. 24 | * Also note that triple-buffering or any other number of buffers can be easily 25 | * implemented by following the scheme here. However, in this example we limit 26 | * the number of buffers to 2 so it is easier to follow. 27 | */ 28 | 29 | #define _GNU_SOURCE 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include 41 | #include 42 | 43 | struct modeset_buf; 44 | struct modeset_dev; 45 | static int modeset_find_crtc(int fd, drmModeRes *res, drmModeConnector *conn, 46 | struct modeset_dev *dev); 47 | static int modeset_create_fb(int fd, struct modeset_buf *buf); 48 | static void modeset_destroy_fb(int fd, struct modeset_buf *buf); 49 | static int modeset_setup_dev(int fd, drmModeRes *res, drmModeConnector *conn, 50 | struct modeset_dev *dev); 51 | static int modeset_open(int *out, const char *node); 52 | static int modeset_prepare(int fd); 53 | static void modeset_draw(int fd); 54 | static void modeset_cleanup(int fd); 55 | 56 | /* 57 | * modeset_open() stays the same as before. 58 | */ 59 | 60 | static int modeset_open(int *out, const char *node) 61 | { 62 | int fd, ret; 63 | uint64_t has_dumb; 64 | 65 | fd = open(node, O_RDWR | O_CLOEXEC); 66 | if (fd < 0) { 67 | ret = -errno; 68 | fprintf(stderr, "cannot open '%s': %m\n", node); 69 | return ret; 70 | } 71 | 72 | if (drmGetCap(fd, DRM_CAP_DUMB_BUFFER, &has_dumb) < 0 || 73 | !has_dumb) { 74 | fprintf(stderr, "drm device '%s' does not support dumb buffers\n", 75 | node); 76 | close(fd); 77 | return -EOPNOTSUPP; 78 | } 79 | 80 | *out = fd; 81 | return 0; 82 | } 83 | 84 | /* 85 | * Previously, we used the modeset_dev objects to hold buffer informations, too. 86 | * Technically, we could have split them but avoided this to make the 87 | * example simpler. 88 | * However, in this example we need 2 buffers. One back buffer and one front 89 | * buffer. So we introduce a new structure modeset_buf which contains everything 90 | * related to a single buffer. Each device now gets an array of two of these 91 | * buffers. 92 | * Each buffer consists of width, height, stride, size, handle, map and fb-id. 93 | * They have the same meaning as before. 94 | * 95 | * Each device also gets a new integer field: front_buf. This field contains the 96 | * index of the buffer that is currently used as front buffer / scanout buffer. 97 | * In our example it can be 0 or 1. We flip it by using XOR: 98 | * dev->front_buf ^= dev->front_buf 99 | * 100 | * Everything else stays the same. 101 | */ 102 | 103 | struct modeset_buf { 104 | uint32_t width; 105 | uint32_t height; 106 | uint32_t stride; 107 | uint32_t size; 108 | uint32_t handle; 109 | uint8_t *map; 110 | uint32_t fb; 111 | }; 112 | 113 | struct modeset_dev { 114 | struct modeset_dev *next; 115 | 116 | unsigned int front_buf; 117 | struct modeset_buf bufs[2]; 118 | 119 | drmModeModeInfo mode; 120 | uint32_t conn; 121 | uint32_t crtc; 122 | drmModeCrtc *saved_crtc; 123 | }; 124 | 125 | static struct modeset_dev *modeset_list = NULL; 126 | 127 | /* 128 | * modeset_prepare() stays the same. 129 | */ 130 | 131 | static int modeset_prepare(int fd) 132 | { 133 | drmModeRes *res; 134 | drmModeConnector *conn; 135 | int i; 136 | struct modeset_dev *dev; 137 | int ret; 138 | 139 | /* retrieve resources */ 140 | res = drmModeGetResources(fd); 141 | if (!res) { 142 | fprintf(stderr, "cannot retrieve DRM resources (%d): %m\n", 143 | errno); 144 | return -errno; 145 | } 146 | 147 | /* iterate all connectors */ 148 | for (i = 0; i < res->count_connectors; ++i) { 149 | /* get information for each connector */ 150 | conn = drmModeGetConnector(fd, res->connectors[i]); 151 | if (!conn) { 152 | fprintf(stderr, "cannot retrieve DRM connector %u:%u (%d): %m\n", 153 | i, res->connectors[i], errno); 154 | continue; 155 | } 156 | 157 | /* create a device structure */ 158 | dev = malloc(sizeof(*dev)); 159 | memset(dev, 0, sizeof(*dev)); 160 | dev->conn = conn->connector_id; 161 | 162 | /* call helper function to prepare this connector */ 163 | ret = modeset_setup_dev(fd, res, conn, dev); 164 | if (ret) { 165 | if (ret != -ENOENT) { 166 | errno = -ret; 167 | fprintf(stderr, "cannot setup device for connector %u:%u (%d): %m\n", 168 | i, res->connectors[i], errno); 169 | } 170 | free(dev); 171 | drmModeFreeConnector(conn); 172 | continue; 173 | } 174 | 175 | /* free connector data and link device into global list */ 176 | drmModeFreeConnector(conn); 177 | dev->next = modeset_list; 178 | modeset_list = dev; 179 | } 180 | 181 | /* free resources again */ 182 | drmModeFreeResources(res); 183 | return 0; 184 | } 185 | 186 | /* 187 | * modeset_setup_dev() sets up all resources for a single device. It mostly 188 | * stays the same, but one thing changes: We allocate two framebuffers instead 189 | * of one. That is, we call modeset_create_fb() twice. 190 | * We also copy the width/height information into both framebuffers so 191 | * modeset_create_fb() can use them without requiring a pointer to modeset_dev. 192 | */ 193 | 194 | static int modeset_setup_dev(int fd, drmModeRes *res, drmModeConnector *conn, 195 | struct modeset_dev *dev) 196 | { 197 | int ret; 198 | 199 | /* check if a monitor is connected */ 200 | if (conn->connection != DRM_MODE_CONNECTED) { 201 | fprintf(stderr, "ignoring unused connector %u\n", 202 | conn->connector_id); 203 | return -ENOENT; 204 | } 205 | 206 | /* check if there is at least one valid mode */ 207 | if (conn->count_modes == 0) { 208 | fprintf(stderr, "no valid mode for connector %u\n", 209 | conn->connector_id); 210 | return -EFAULT; 211 | } 212 | 213 | /* copy the mode information into our device structure and into both 214 | * buffers */ 215 | memcpy(&dev->mode, &conn->modes[0], sizeof(dev->mode)); 216 | dev->bufs[0].width = conn->modes[0].hdisplay; 217 | dev->bufs[0].height = conn->modes[0].vdisplay; 218 | dev->bufs[1].width = conn->modes[0].hdisplay; 219 | dev->bufs[1].height = conn->modes[0].vdisplay; 220 | fprintf(stderr, "mode for connector %u is %ux%u\n", 221 | conn->connector_id, dev->bufs[0].width, dev->bufs[0].height); 222 | 223 | /* find a crtc for this connector */ 224 | ret = modeset_find_crtc(fd, res, conn, dev); 225 | if (ret) { 226 | fprintf(stderr, "no valid crtc for connector %u\n", 227 | conn->connector_id); 228 | return ret; 229 | } 230 | 231 | /* create framebuffer #1 for this CRTC */ 232 | ret = modeset_create_fb(fd, &dev->bufs[0]); 233 | if (ret) { 234 | fprintf(stderr, "cannot create framebuffer for connector %u\n", 235 | conn->connector_id); 236 | return ret; 237 | } 238 | 239 | /* create framebuffer #2 for this CRTC */ 240 | ret = modeset_create_fb(fd, &dev->bufs[1]); 241 | if (ret) { 242 | fprintf(stderr, "cannot create framebuffer for connector %u\n", 243 | conn->connector_id); 244 | modeset_destroy_fb(fd, &dev->bufs[0]); 245 | return ret; 246 | } 247 | 248 | return 0; 249 | } 250 | 251 | /* 252 | * modeset_find_crtc() stays the same. 253 | */ 254 | 255 | static int modeset_find_crtc(int fd, drmModeRes *res, drmModeConnector *conn, 256 | struct modeset_dev *dev) 257 | { 258 | drmModeEncoder *enc; 259 | int i, j; 260 | uint32_t crtc; 261 | struct modeset_dev *iter; 262 | 263 | /* first try the currently conected encoder+crtc */ 264 | if (conn->encoder_id) 265 | enc = drmModeGetEncoder(fd, conn->encoder_id); 266 | else 267 | enc = NULL; 268 | 269 | if (enc) { 270 | if (enc->crtc_id) { 271 | crtc = enc->crtc_id; 272 | for (iter = modeset_list; iter; iter = iter->next) { 273 | if (iter->crtc == crtc) { 274 | crtc = -1; 275 | break; 276 | } 277 | } 278 | 279 | if (crtc >= 0) { 280 | drmModeFreeEncoder(enc); 281 | dev->crtc = crtc; 282 | return 0; 283 | } 284 | } 285 | 286 | drmModeFreeEncoder(enc); 287 | } 288 | 289 | /* If the connector is not currently bound to an encoder or if the 290 | * encoder+crtc is already used by another connector (actually unlikely 291 | * but lets be safe), iterate all other available encoders to find a 292 | * matching CRTC. */ 293 | for (i = 0; i < conn->count_encoders; ++i) { 294 | enc = drmModeGetEncoder(fd, conn->encoders[i]); 295 | if (!enc) { 296 | fprintf(stderr, "cannot retrieve encoder %u:%u (%d): %m\n", 297 | i, conn->encoders[i], errno); 298 | continue; 299 | } 300 | 301 | /* iterate all global CRTCs */ 302 | for (j = 0; j < res->count_crtcs; ++j) { 303 | /* check whether this CRTC works with the encoder */ 304 | if (!(enc->possible_crtcs & (1 << j))) 305 | continue; 306 | 307 | /* check that no other device already uses this CRTC */ 308 | crtc = res->crtcs[j]; 309 | for (iter = modeset_list; iter; iter = iter->next) { 310 | if (iter->crtc == crtc) { 311 | crtc = -1; 312 | break; 313 | } 314 | } 315 | 316 | /* we have found a CRTC, so save it and return */ 317 | if (crtc >= 0) { 318 | drmModeFreeEncoder(enc); 319 | dev->crtc = crtc; 320 | return 0; 321 | } 322 | } 323 | 324 | drmModeFreeEncoder(enc); 325 | } 326 | 327 | fprintf(stderr, "cannot find suitable CRTC for connector %u\n", 328 | conn->connector_id); 329 | return -ENOENT; 330 | } 331 | 332 | /* 333 | * modeset_create_fb() is mostly the same as before. Buf instead of writing the 334 | * fields of a modeset_dev, we now require a buffer pointer passed as @buf. 335 | * Please note that buf->width and buf->height are initialized by 336 | * modeset_setup_dev() so we can use them here. 337 | */ 338 | 339 | static int modeset_create_fb(int fd, struct modeset_buf *buf) 340 | { 341 | struct drm_mode_create_dumb creq; 342 | struct drm_mode_destroy_dumb dreq; 343 | struct drm_mode_map_dumb mreq; 344 | int ret; 345 | 346 | /* create dumb buffer */ 347 | memset(&creq, 0, sizeof(creq)); 348 | creq.width = buf->width; 349 | creq.height = buf->height; 350 | creq.bpp = 32; 351 | ret = drmIoctl(fd, DRM_IOCTL_MODE_CREATE_DUMB, &creq); 352 | if (ret < 0) { 353 | fprintf(stderr, "cannot create dumb buffer (%d): %m\n", 354 | errno); 355 | return -errno; 356 | } 357 | buf->stride = creq.pitch; 358 | buf->size = creq.size; 359 | buf->handle = creq.handle; 360 | 361 | /* create framebuffer object for the dumb-buffer */ 362 | ret = drmModeAddFB(fd, buf->width, buf->height, 24, 32, buf->stride, 363 | buf->handle, &buf->fb); 364 | if (ret) { 365 | fprintf(stderr, "cannot create framebuffer (%d): %m\n", 366 | errno); 367 | ret = -errno; 368 | goto err_destroy; 369 | } 370 | 371 | /* prepare buffer for memory mapping */ 372 | memset(&mreq, 0, sizeof(mreq)); 373 | mreq.handle = buf->handle; 374 | ret = drmIoctl(fd, DRM_IOCTL_MODE_MAP_DUMB, &mreq); 375 | if (ret) { 376 | fprintf(stderr, "cannot map dumb buffer (%d): %m\n", 377 | errno); 378 | ret = -errno; 379 | goto err_fb; 380 | } 381 | 382 | /* perform actual memory mapping */ 383 | buf->map = mmap(0, buf->size, PROT_READ | PROT_WRITE, MAP_SHARED, 384 | fd, mreq.offset); 385 | if (buf->map == MAP_FAILED) { 386 | fprintf(stderr, "cannot mmap dumb buffer (%d): %m\n", 387 | errno); 388 | ret = -errno; 389 | goto err_fb; 390 | } 391 | 392 | /* clear the framebuffer to 0 */ 393 | memset(buf->map, 0, buf->size); 394 | 395 | return 0; 396 | 397 | err_fb: 398 | drmModeRmFB(fd, buf->fb); 399 | err_destroy: 400 | memset(&dreq, 0, sizeof(dreq)); 401 | dreq.handle = buf->handle; 402 | drmIoctl(fd, DRM_IOCTL_MODE_DESTROY_DUMB, &dreq); 403 | return ret; 404 | } 405 | 406 | /* 407 | * modeset_destroy_fb() is a new function. It does exactly the reverse of 408 | * modeset_create_fb() and destroys a single framebuffer. The modeset.c example 409 | * used to do this directly in modeset_cleanup(). 410 | * We simply unmap the buffer, remove the drm-FB and destroy the memory buffer. 411 | */ 412 | 413 | static void modeset_destroy_fb(int fd, struct modeset_buf *buf) 414 | { 415 | struct drm_mode_destroy_dumb dreq; 416 | 417 | /* unmap buffer */ 418 | munmap(buf->map, buf->size); 419 | 420 | /* delete framebuffer */ 421 | drmModeRmFB(fd, buf->fb); 422 | 423 | /* delete dumb buffer */ 424 | memset(&dreq, 0, sizeof(dreq)); 425 | dreq.handle = buf->handle; 426 | drmIoctl(fd, DRM_IOCTL_MODE_DESTROY_DUMB, &dreq); 427 | } 428 | 429 | /* 430 | * main() also stays almost exactly the same as before. We only need to change 431 | * the way that we initially set the CRTCs. Instead of using the buffer 432 | * information from modeset_dev, we now use dev->bufs[iter->front_buf] to get 433 | * the current front-buffer and use this framebuffer for drmModeSetCrtc(). 434 | */ 435 | 436 | int main(int argc, char **argv) 437 | { 438 | int ret, fd; 439 | const char *card; 440 | struct modeset_dev *iter; 441 | struct modeset_buf *buf; 442 | 443 | /* check which DRM device to open */ 444 | if (argc > 1) 445 | card = argv[1]; 446 | else 447 | card = "/dev/dri/card0"; 448 | 449 | fprintf(stderr, "using card '%s'\n", card); 450 | 451 | /* open the DRM device */ 452 | ret = modeset_open(&fd, card); 453 | if (ret) 454 | goto out_return; 455 | 456 | /* prepare all connectors and CRTCs */ 457 | ret = modeset_prepare(fd); 458 | if (ret) 459 | goto out_close; 460 | 461 | /* perform actual modesetting on each found connector+CRTC */ 462 | for (iter = modeset_list; iter; iter = iter->next) { 463 | iter->saved_crtc = drmModeGetCrtc(fd, iter->crtc); 464 | buf = &iter->bufs[iter->front_buf]; 465 | ret = drmModeSetCrtc(fd, iter->crtc, buf->fb, 0, 0, 466 | &iter->conn, 1, &iter->mode); 467 | if (ret) 468 | fprintf(stderr, "cannot set CRTC for connector %u (%d): %m\n", 469 | iter->conn, errno); 470 | } 471 | 472 | /* draw some colors for 5seconds */ 473 | modeset_draw(fd); 474 | 475 | /* cleanup everything */ 476 | modeset_cleanup(fd); 477 | 478 | ret = 0; 479 | 480 | out_close: 481 | close(fd); 482 | out_return: 483 | if (ret) { 484 | errno = -ret; 485 | fprintf(stderr, "modeset failed with error %d: %m\n", errno); 486 | } else { 487 | fprintf(stderr, "exiting\n"); 488 | } 489 | return ret; 490 | } 491 | 492 | /* 493 | * A short helper function to compute a changing color value. No need to 494 | * understand it. 495 | */ 496 | 497 | static uint8_t next_color(bool *up, uint8_t cur, unsigned int mod) 498 | { 499 | uint8_t next; 500 | 501 | next = cur + (*up ? 1 : -1) * (rand() % mod); 502 | if ((*up && next < cur) || (!*up && next > cur)) { 503 | *up = !*up; 504 | next = cur; 505 | } 506 | 507 | return next; 508 | } 509 | 510 | /* 511 | * modeset_draw() is the place where things change. The render-logic is the same 512 | * and we still draw a solid-color on the whole screen. However, we now have two 513 | * buffers and need to flip between them. 514 | * 515 | * So before drawing into a framebuffer, we need to find the back-buffer. 516 | * Remember, dev->font_buf is the index of the front buffer, so 517 | * dev->front_buf ^ 1 is the index of the back buffer. We simply use 518 | * dev->bufs[dev->front_buf ^ 1] to get the back-buffer and draw into it. 519 | * 520 | * After we finished drawing, we need to flip the buffers. We do this with the 521 | * same call as we initially set the CRTC: drmModeSetCrtc(). However, we now 522 | * pass the back-buffer as new framebuffer as we want to flip them. 523 | * The only thing left to do is to change the dev->front_buf index to point to 524 | * the new back-buffer (which was previously the front buffer). 525 | * We then sleep for a short time period and start drawing again. 526 | * 527 | * If you run this example, you will notice that there is almost no flickering, 528 | * anymore. The buffers are now swapped as a whole so each new frame shows 529 | * always the whole new image. If you look carefully, you will notice that the 530 | * modeset.c example showed many screen corruptions during redraw-cycles. 531 | * 532 | * However, this example is still not perfect. Imagine the display-controller is 533 | * currently scanning out a new image and we call drmModeSetCrtc() 534 | * simultaneously. It will then have the same effect as if we used a single 535 | * buffer and we get some tearing. But, the chance that this happens is a lot 536 | * less likely as with a single-buffer. This is because there is a long period 537 | * between each frame called vertical-blank where the display-controller does 538 | * not perform a scanout. If we swap the buffers in this period, we have the 539 | * guarantee that there will be no tearing. See the modeset-vsync.c example if 540 | * you want to know how you can guarantee that the swap takes place at a 541 | * vertical-sync. 542 | */ 543 | 544 | static void modeset_draw(int fd) 545 | { 546 | uint8_t r, g, b; 547 | bool r_up, g_up, b_up; 548 | unsigned int i, j, k, off; 549 | struct modeset_dev *iter; 550 | struct modeset_buf *buf; 551 | int ret; 552 | 553 | srand(time(NULL)); 554 | r = rand() % 0xff; 555 | g = rand() % 0xff; 556 | b = rand() % 0xff; 557 | r_up = g_up = b_up = true; 558 | 559 | for (i = 0; i < 50; ++i) { 560 | r = next_color(&r_up, r, 20); 561 | g = next_color(&g_up, g, 10); 562 | b = next_color(&b_up, b, 5); 563 | 564 | for (iter = modeset_list; iter; iter = iter->next) { 565 | buf = &iter->bufs[iter->front_buf ^ 1]; 566 | for (j = 0; j < buf->height; ++j) { 567 | for (k = 0; k < buf->width; ++k) { 568 | off = buf->stride * j + k * 4; 569 | *(uint32_t*)&buf->map[off] = 570 | (r << 16) | (g << 8) | b; 571 | } 572 | } 573 | 574 | ret = drmModeSetCrtc(fd, iter->crtc, buf->fb, 0, 0, 575 | &iter->conn, 1, &iter->mode); 576 | if (ret) 577 | fprintf(stderr, "cannot flip CRTC for connector %u (%d): %m\n", 578 | iter->conn, errno); 579 | else 580 | iter->front_buf ^= 1; 581 | } 582 | 583 | usleep(100000); 584 | } 585 | } 586 | 587 | /* 588 | * modeset_cleanup() stays the same as before. But it now calls 589 | * modeset_destroy_fb() instead of accessing the framebuffers directly. 590 | */ 591 | 592 | static void modeset_cleanup(int fd) 593 | { 594 | struct modeset_dev *iter; 595 | 596 | while (modeset_list) { 597 | /* remove from global list */ 598 | iter = modeset_list; 599 | modeset_list = iter->next; 600 | 601 | /* restore saved CRTC configuration */ 602 | drmModeSetCrtc(fd, 603 | iter->saved_crtc->crtc_id, 604 | iter->saved_crtc->buffer_id, 605 | iter->saved_crtc->x, 606 | iter->saved_crtc->y, 607 | &iter->conn, 608 | 1, 609 | &iter->saved_crtc->mode); 610 | drmModeFreeCrtc(iter->saved_crtc); 611 | 612 | /* destroy framebuffers */ 613 | modeset_destroy_fb(fd, &iter->bufs[1]); 614 | modeset_destroy_fb(fd, &iter->bufs[0]); 615 | 616 | /* free allocated memory */ 617 | free(iter); 618 | } 619 | } 620 | 621 | /* 622 | * This was a very short extension to the basic modesetting example that shows 623 | * how double-buffering is implemented. Double-buffering is the de-facto 624 | * standard in any graphics application so any other example will be based on 625 | * this. It is important to understand the ideas behind it as the code is pretty 626 | * easy and short compared to modeset.c. 627 | * 628 | * Double-buffering doesn't solve all problems. Vsync'ed page-flips solve most 629 | * of the problems that still occur, but has problems on it's own (see 630 | * modeset-vsync.c for a discussion). 631 | * 632 | * If you want more code, I can recommend reading the source-code of: 633 | * - plymouth (which uses dumb-buffers like this example; very easy to understand) 634 | * - kmscon (which uses libuterm to do this) 635 | * - wayland (very sophisticated DRM renderer; hard to understand fully as it 636 | * uses more complicated techniques like DRM planes) 637 | * - xserver (very hard to understand as it is split across many files/projects) 638 | * 639 | * Any feedback is welcome. Feel free to use this code freely for your own 640 | * documentation or projects. 641 | * 642 | * - Hosted on http://github.com/dvdhrm/docs 643 | * - Written by David Herrmann 644 | */ 645 | -------------------------------------------------------------------------------- /drm-howto/modeset-vsync.c: -------------------------------------------------------------------------------- 1 | /* 2 | * modeset - DRM Double-Buffered VSync'ed Modesetting Example 3 | * 4 | * Written 2012 by David Herrmann 5 | * Dedicated to the Public Domain. 6 | */ 7 | 8 | /* 9 | * DRM Double-Buffered VSync'ed Modesetting Howto 10 | * This example extends modeset-double-buffered.c and introduces page-flips 11 | * synced with vertical-blanks (vsync'ed). A vertical-blank is the time-period 12 | * when a display-controller pauses from scanning out the framebuffer. After the 13 | * vertical-blank is over, the framebuffer is again scanned out line by line and 14 | * followed again by a vertical-blank. 15 | * 16 | * Vertical-blanks are important when changing a framebuffer. We already 17 | * introduced double-buffering, so this example shows how we can flip the 18 | * buffers during a vertical blank and _not_ during the scanout period. 19 | * 20 | * This example assumes that you are familiar with modeset-double-buffered. Only 21 | * the differences between both files are highlighted here. 22 | */ 23 | 24 | #define _GNU_SOURCE 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | 38 | struct modeset_buf; 39 | struct modeset_dev; 40 | static int modeset_find_crtc(int fd, drmModeRes *res, drmModeConnector *conn, 41 | struct modeset_dev *dev); 42 | static int modeset_create_fb(int fd, struct modeset_buf *buf); 43 | static void modeset_destroy_fb(int fd, struct modeset_buf *buf); 44 | static int modeset_setup_dev(int fd, drmModeRes *res, drmModeConnector *conn, 45 | struct modeset_dev *dev); 46 | static int modeset_open(int *out, const char *node); 47 | static int modeset_prepare(int fd); 48 | static void modeset_draw(int fd); 49 | static void modeset_draw_dev(int fd, struct modeset_dev *dev); 50 | static void modeset_cleanup(int fd); 51 | 52 | /* 53 | * modeset_open() stays the same. 54 | */ 55 | 56 | static int modeset_open(int *out, const char *node) 57 | { 58 | int fd, ret; 59 | uint64_t has_dumb; 60 | 61 | fd = open(node, O_RDWR | O_CLOEXEC); 62 | if (fd < 0) { 63 | ret = -errno; 64 | fprintf(stderr, "cannot open '%s': %m\n", node); 65 | return ret; 66 | } 67 | 68 | if (drmGetCap(fd, DRM_CAP_DUMB_BUFFER, &has_dumb) < 0 || 69 | !has_dumb) { 70 | fprintf(stderr, "drm device '%s' does not support dumb buffers\n", 71 | node); 72 | close(fd); 73 | return -EOPNOTSUPP; 74 | } 75 | 76 | *out = fd; 77 | return 0; 78 | } 79 | 80 | /* 81 | * modeset_buf and modeset_dev stay mostly the same. But 6 new fields are added 82 | * to modeset_dev: r, g, b, r_up, g_up, b_up. They are used to compute the 83 | * current color that is drawn on this output device. You can ignore them as 84 | * they aren't important for this example. 85 | * The modeset-double-buffered.c example used exactly the same fields but as 86 | * local variables in modeset_draw(). 87 | * 88 | * The \pflip_pending variable is true when a page-flip is currently pending, 89 | * that is, the kernel will flip buffers on the next vertical blank. The 90 | * \cleanup variable is true if the device is currently cleaned up and no more 91 | * pageflips should be scheduled. They are used to synchronize the cleanup 92 | * routines. 93 | */ 94 | 95 | struct modeset_buf { 96 | uint32_t width; 97 | uint32_t height; 98 | uint32_t stride; 99 | uint32_t size; 100 | uint32_t handle; 101 | uint8_t *map; 102 | uint32_t fb; 103 | }; 104 | 105 | struct modeset_dev { 106 | struct modeset_dev *next; 107 | 108 | unsigned int front_buf; 109 | struct modeset_buf bufs[2]; 110 | 111 | drmModeModeInfo mode; 112 | uint32_t conn; 113 | uint32_t crtc; 114 | drmModeCrtc *saved_crtc; 115 | 116 | bool pflip_pending; 117 | bool cleanup; 118 | 119 | uint8_t r, g, b; 120 | bool r_up, g_up, b_up; 121 | }; 122 | 123 | static struct modeset_dev *modeset_list = NULL; 124 | 125 | /* 126 | * modeset_prepare() stays the same. 127 | */ 128 | 129 | static int modeset_prepare(int fd) 130 | { 131 | drmModeRes *res; 132 | drmModeConnector *conn; 133 | int i; 134 | struct modeset_dev *dev; 135 | int ret; 136 | 137 | /* retrieve resources */ 138 | res = drmModeGetResources(fd); 139 | if (!res) { 140 | fprintf(stderr, "cannot retrieve DRM resources (%d): %m\n", 141 | errno); 142 | return -errno; 143 | } 144 | 145 | /* iterate all connectors */ 146 | for (i = 0; i < res->count_connectors; ++i) { 147 | /* get information for each connector */ 148 | conn = drmModeGetConnector(fd, res->connectors[i]); 149 | if (!conn) { 150 | fprintf(stderr, "cannot retrieve DRM connector %u:%u (%d): %m\n", 151 | i, res->connectors[i], errno); 152 | continue; 153 | } 154 | 155 | /* create a device structure */ 156 | dev = malloc(sizeof(*dev)); 157 | memset(dev, 0, sizeof(*dev)); 158 | dev->conn = conn->connector_id; 159 | 160 | /* call helper function to prepare this connector */ 161 | ret = modeset_setup_dev(fd, res, conn, dev); 162 | if (ret) { 163 | if (ret != -ENOENT) { 164 | errno = -ret; 165 | fprintf(stderr, "cannot setup device for connector %u:%u (%d): %m\n", 166 | i, res->connectors[i], errno); 167 | } 168 | free(dev); 169 | drmModeFreeConnector(conn); 170 | continue; 171 | } 172 | 173 | /* free connector data and link device into global list */ 174 | drmModeFreeConnector(conn); 175 | dev->next = modeset_list; 176 | modeset_list = dev; 177 | } 178 | 179 | /* free resources again */ 180 | drmModeFreeResources(res); 181 | return 0; 182 | } 183 | 184 | /* 185 | * modeset_setup_dev() stays the same. 186 | */ 187 | 188 | static int modeset_setup_dev(int fd, drmModeRes *res, drmModeConnector *conn, 189 | struct modeset_dev *dev) 190 | { 191 | int ret; 192 | 193 | /* check if a monitor is connected */ 194 | if (conn->connection != DRM_MODE_CONNECTED) { 195 | fprintf(stderr, "ignoring unused connector %u\n", 196 | conn->connector_id); 197 | return -ENOENT; 198 | } 199 | 200 | /* check if there is at least one valid mode */ 201 | if (conn->count_modes == 0) { 202 | fprintf(stderr, "no valid mode for connector %u\n", 203 | conn->connector_id); 204 | return -EFAULT; 205 | } 206 | 207 | /* copy the mode information into our device structure and into both 208 | * buffers */ 209 | memcpy(&dev->mode, &conn->modes[0], sizeof(dev->mode)); 210 | dev->bufs[0].width = conn->modes[0].hdisplay; 211 | dev->bufs[0].height = conn->modes[0].vdisplay; 212 | dev->bufs[1].width = conn->modes[0].hdisplay; 213 | dev->bufs[1].height = conn->modes[0].vdisplay; 214 | fprintf(stderr, "mode for connector %u is %ux%u\n", 215 | conn->connector_id, dev->bufs[0].width, dev->bufs[0].height); 216 | 217 | /* find a crtc for this connector */ 218 | ret = modeset_find_crtc(fd, res, conn, dev); 219 | if (ret) { 220 | fprintf(stderr, "no valid crtc for connector %u\n", 221 | conn->connector_id); 222 | return ret; 223 | } 224 | 225 | /* create framebuffer #1 for this CRTC */ 226 | ret = modeset_create_fb(fd, &dev->bufs[0]); 227 | if (ret) { 228 | fprintf(stderr, "cannot create framebuffer for connector %u\n", 229 | conn->connector_id); 230 | return ret; 231 | } 232 | 233 | /* create framebuffer #2 for this CRTC */ 234 | ret = modeset_create_fb(fd, &dev->bufs[1]); 235 | if (ret) { 236 | fprintf(stderr, "cannot create framebuffer for connector %u\n", 237 | conn->connector_id); 238 | modeset_destroy_fb(fd, &dev->bufs[0]); 239 | return ret; 240 | } 241 | 242 | return 0; 243 | } 244 | 245 | /* 246 | * modeset_find_crtc() stays the same. 247 | */ 248 | 249 | static int modeset_find_crtc(int fd, drmModeRes *res, drmModeConnector *conn, 250 | struct modeset_dev *dev) 251 | { 252 | drmModeEncoder *enc; 253 | int i, j; 254 | uint32_t crtc; 255 | struct modeset_dev *iter; 256 | 257 | /* first try the currently conected encoder+crtc */ 258 | if (conn->encoder_id) 259 | enc = drmModeGetEncoder(fd, conn->encoder_id); 260 | else 261 | enc = NULL; 262 | 263 | if (enc) { 264 | if (enc->crtc_id) { 265 | crtc = enc->crtc_id; 266 | for (iter = modeset_list; iter; iter = iter->next) { 267 | if (iter->crtc == crtc) { 268 | crtc = -1; 269 | break; 270 | } 271 | } 272 | 273 | if (crtc >= 0) { 274 | drmModeFreeEncoder(enc); 275 | dev->crtc = crtc; 276 | return 0; 277 | } 278 | } 279 | 280 | drmModeFreeEncoder(enc); 281 | } 282 | 283 | /* If the connector is not currently bound to an encoder or if the 284 | * encoder+crtc is already used by another connector (actually unlikely 285 | * but lets be safe), iterate all other available encoders to find a 286 | * matching CRTC. */ 287 | for (i = 0; i < conn->count_encoders; ++i) { 288 | enc = drmModeGetEncoder(fd, conn->encoders[i]); 289 | if (!enc) { 290 | fprintf(stderr, "cannot retrieve encoder %u:%u (%d): %m\n", 291 | i, conn->encoders[i], errno); 292 | continue; 293 | } 294 | 295 | /* iterate all global CRTCs */ 296 | for (j = 0; j < res->count_crtcs; ++j) { 297 | /* check whether this CRTC works with the encoder */ 298 | if (!(enc->possible_crtcs & (1 << j))) 299 | continue; 300 | 301 | /* check that no other device already uses this CRTC */ 302 | crtc = res->crtcs[j]; 303 | for (iter = modeset_list; iter; iter = iter->next) { 304 | if (iter->crtc == crtc) { 305 | crtc = -1; 306 | break; 307 | } 308 | } 309 | 310 | /* we have found a CRTC, so save it and return */ 311 | if (crtc >= 0) { 312 | drmModeFreeEncoder(enc); 313 | dev->crtc = crtc; 314 | return 0; 315 | } 316 | } 317 | 318 | drmModeFreeEncoder(enc); 319 | } 320 | 321 | fprintf(stderr, "cannot find suitable CRTC for connector %u\n", 322 | conn->connector_id); 323 | return -ENOENT; 324 | } 325 | 326 | /* 327 | * modeset_create_fb() stays the same. 328 | */ 329 | 330 | static int modeset_create_fb(int fd, struct modeset_buf *buf) 331 | { 332 | struct drm_mode_create_dumb creq; 333 | struct drm_mode_destroy_dumb dreq; 334 | struct drm_mode_map_dumb mreq; 335 | int ret; 336 | 337 | /* create dumb buffer */ 338 | memset(&creq, 0, sizeof(creq)); 339 | creq.width = buf->width; 340 | creq.height = buf->height; 341 | creq.bpp = 32; 342 | ret = drmIoctl(fd, DRM_IOCTL_MODE_CREATE_DUMB, &creq); 343 | if (ret < 0) { 344 | fprintf(stderr, "cannot create dumb buffer (%d): %m\n", 345 | errno); 346 | return -errno; 347 | } 348 | buf->stride = creq.pitch; 349 | buf->size = creq.size; 350 | buf->handle = creq.handle; 351 | 352 | /* create framebuffer object for the dumb-buffer */ 353 | ret = drmModeAddFB(fd, buf->width, buf->height, 24, 32, buf->stride, 354 | buf->handle, &buf->fb); 355 | if (ret) { 356 | fprintf(stderr, "cannot create framebuffer (%d): %m\n", 357 | errno); 358 | ret = -errno; 359 | goto err_destroy; 360 | } 361 | 362 | /* prepare buffer for memory mapping */ 363 | memset(&mreq, 0, sizeof(mreq)); 364 | mreq.handle = buf->handle; 365 | ret = drmIoctl(fd, DRM_IOCTL_MODE_MAP_DUMB, &mreq); 366 | if (ret) { 367 | fprintf(stderr, "cannot map dumb buffer (%d): %m\n", 368 | errno); 369 | ret = -errno; 370 | goto err_fb; 371 | } 372 | 373 | /* perform actual memory mapping */ 374 | buf->map = mmap(0, buf->size, PROT_READ | PROT_WRITE, MAP_SHARED, 375 | fd, mreq.offset); 376 | if (buf->map == MAP_FAILED) { 377 | fprintf(stderr, "cannot mmap dumb buffer (%d): %m\n", 378 | errno); 379 | ret = -errno; 380 | goto err_fb; 381 | } 382 | 383 | /* clear the framebuffer to 0 */ 384 | memset(buf->map, 0, buf->size); 385 | 386 | return 0; 387 | 388 | err_fb: 389 | drmModeRmFB(fd, buf->fb); 390 | err_destroy: 391 | memset(&dreq, 0, sizeof(dreq)); 392 | dreq.handle = buf->handle; 393 | drmIoctl(fd, DRM_IOCTL_MODE_DESTROY_DUMB, &dreq); 394 | return ret; 395 | } 396 | 397 | /* 398 | * modeset_destroy_fb() stays the same. 399 | */ 400 | 401 | static void modeset_destroy_fb(int fd, struct modeset_buf *buf) 402 | { 403 | struct drm_mode_destroy_dumb dreq; 404 | 405 | /* unmap buffer */ 406 | munmap(buf->map, buf->size); 407 | 408 | /* delete framebuffer */ 409 | drmModeRmFB(fd, buf->fb); 410 | 411 | /* delete dumb buffer */ 412 | memset(&dreq, 0, sizeof(dreq)); 413 | dreq.handle = buf->handle; 414 | drmIoctl(fd, DRM_IOCTL_MODE_DESTROY_DUMB, &dreq); 415 | } 416 | 417 | /* 418 | * main() also stays the same. 419 | */ 420 | 421 | int main(int argc, char **argv) 422 | { 423 | int ret, fd; 424 | const char *card; 425 | struct modeset_dev *iter; 426 | struct modeset_buf *buf; 427 | 428 | /* check which DRM device to open */ 429 | if (argc > 1) 430 | card = argv[1]; 431 | else 432 | card = "/dev/dri/card0"; 433 | 434 | fprintf(stderr, "using card '%s'\n", card); 435 | 436 | /* open the DRM device */ 437 | ret = modeset_open(&fd, card); 438 | if (ret) 439 | goto out_return; 440 | 441 | /* prepare all connectors and CRTCs */ 442 | ret = modeset_prepare(fd); 443 | if (ret) 444 | goto out_close; 445 | 446 | /* perform actual modesetting on each found connector+CRTC */ 447 | for (iter = modeset_list; iter; iter = iter->next) { 448 | iter->saved_crtc = drmModeGetCrtc(fd, iter->crtc); 449 | buf = &iter->bufs[iter->front_buf]; 450 | ret = drmModeSetCrtc(fd, iter->crtc, buf->fb, 0, 0, 451 | &iter->conn, 1, &iter->mode); 452 | if (ret) 453 | fprintf(stderr, "cannot set CRTC for connector %u (%d): %m\n", 454 | iter->conn, errno); 455 | } 456 | 457 | /* draw some colors for 5seconds */ 458 | modeset_draw(fd); 459 | 460 | /* cleanup everything */ 461 | modeset_cleanup(fd); 462 | 463 | ret = 0; 464 | 465 | out_close: 466 | close(fd); 467 | out_return: 468 | if (ret) { 469 | errno = -ret; 470 | fprintf(stderr, "modeset failed with error %d: %m\n", errno); 471 | } else { 472 | fprintf(stderr, "exiting\n"); 473 | } 474 | return ret; 475 | } 476 | 477 | /* 478 | * modeset_page_flip_event() is a callback-helper for modeset_draw() below. 479 | * Please see modeset_draw() for more information. 480 | * 481 | * Note that this does nothing if the device is currently cleaned up. This 482 | * allows to wait for outstanding page-flips during cleanup. 483 | */ 484 | 485 | static void modeset_page_flip_event(int fd, unsigned int frame, 486 | unsigned int sec, unsigned int usec, 487 | void *data) 488 | { 489 | struct modeset_dev *dev = data; 490 | 491 | dev->pflip_pending = false; 492 | if (!dev->cleanup) 493 | modeset_draw_dev(fd, dev); 494 | } 495 | 496 | /* 497 | * modeset_draw() changes heavily from all previous examples. The rendering has 498 | * moved into another helper modeset_draw_dev() below, but modeset_draw() is now 499 | * responsible of controlling when we have to redraw the outputs. 500 | * 501 | * So what we do: first redraw all outputs. We initialize the r/g/b/_up 502 | * variables of each output first, although, you can safely ignore these. 503 | * They're solely used to compute the next color. Then we call 504 | * modeset_draw_dev() for each output. This function _always_ redraws the output 505 | * and schedules a buffer-swap/flip for the next vertical-blank. 506 | * We now have to wait for each vertical-blank to happen so we can draw the next 507 | * frame. If a vblank happens, we simply call modeset_draw_dev() again and wait 508 | * for the next vblank. 509 | * 510 | * Note: Different monitors can have different refresh-rates. That means, a 511 | * vblank event is always assigned to a CRTC. Hence, we get different vblank 512 | * events for each CRTC/modeset_dev that we use. This also means, that our 513 | * framerate-controlled color-morphing is different on each monitor. If you want 514 | * exactly the same frame on all monitors, we would have to share the 515 | * color-values between all devices. However, for simplicity reasons, we don't 516 | * do this here. 517 | * 518 | * So the last piece missing is how we get vblank events. libdrm provides 519 | * drmWaitVBlank(), however, we aren't interested in _all_ vblanks, but only in 520 | * the vblanks for our page-flips. We could use drmWaitVBlank() but there is a 521 | * more convenient way: drmModePageFlip() 522 | * drmModePageFlip() schedules a buffer-flip for the next vblank and then 523 | * notifies us about it. It takes a CRTC-id, fb-id and an arbitrary 524 | * data-pointer and then schedules the page-flip. This is fully asynchronous and 525 | * returns immediately. 526 | * When the page-flip happens, the DRM-fd will become readable and we can call 527 | * drmHandleEvent(). This will read all vblank/page-flip events and call our 528 | * modeset_page_flip_event() callback with the data-pointer that we passed to 529 | * drmModePageFlip(). We simply call modeset_draw_dev() then so the next frame 530 | * is rendered.. 531 | * 532 | * 533 | * So modeset_draw() is reponsible of waiting for the page-flip/vblank events 534 | * for _all_ currently used output devices and schedule a redraw for them. We 535 | * could easily do this in a while (1) { drmHandleEvent() } loop, however, this 536 | * example shows how you can use the DRM-fd to integrate this into your own 537 | * main-loop. If you aren't familiar with select(), poll() or epoll, please read 538 | * it up somewhere else. There is plenty of documentation elsewhere on the 539 | * internet. 540 | * 541 | * So what we do is adding the DRM-fd and the keyboard-input-fd (more precisely: 542 | * the stdin FD) to a select-set and then we wait on this set. If the DRM-fd is 543 | * readable, we call drmHandleEvents() to handle the page-flip events. If the 544 | * input-fd is readable, we exit. So on any keyboard input we exit this loop 545 | * (you need to press RETURN after each keyboard input to make this work). 546 | */ 547 | 548 | static void modeset_draw(int fd) 549 | { 550 | int ret; 551 | fd_set fds; 552 | time_t start, cur; 553 | struct timeval v; 554 | drmEventContext ev; 555 | struct modeset_dev *iter; 556 | 557 | /* init variables */ 558 | srand(time(&start)); 559 | FD_ZERO(&fds); 560 | memset(&v, 0, sizeof(v)); 561 | memset(&ev, 0, sizeof(ev)); 562 | /* Set this to only the latest version you support. Version 2 563 | * introduced the page_flip_handler, so we use that. */ 564 | ev.version = 2; 565 | ev.page_flip_handler = modeset_page_flip_event; 566 | 567 | /* redraw all outputs */ 568 | for (iter = modeset_list; iter; iter = iter->next) { 569 | iter->r = rand() % 0xff; 570 | iter->g = rand() % 0xff; 571 | iter->b = rand() % 0xff; 572 | iter->r_up = iter->g_up = iter->b_up = true; 573 | 574 | modeset_draw_dev(fd, iter); 575 | } 576 | 577 | /* wait 5s for VBLANK or input events */ 578 | while (time(&cur) < start + 5) { 579 | FD_SET(0, &fds); 580 | FD_SET(fd, &fds); 581 | v.tv_sec = start + 5 - cur; 582 | 583 | ret = select(fd + 1, &fds, NULL, NULL, &v); 584 | if (ret < 0) { 585 | fprintf(stderr, "select() failed with %d: %m\n", errno); 586 | break; 587 | } else if (FD_ISSET(0, &fds)) { 588 | fprintf(stderr, "exit due to user-input\n"); 589 | break; 590 | } else if (FD_ISSET(fd, &fds)) { 591 | drmHandleEvent(fd, &ev); 592 | } 593 | } 594 | } 595 | 596 | /* 597 | * A short helper function to compute a changing color value. No need to 598 | * understand it. 599 | */ 600 | 601 | static uint8_t next_color(bool *up, uint8_t cur, unsigned int mod) 602 | { 603 | uint8_t next; 604 | 605 | next = cur + (*up ? 1 : -1) * (rand() % mod); 606 | if ((*up && next < cur) || (!*up && next > cur)) { 607 | *up = !*up; 608 | next = cur; 609 | } 610 | 611 | return next; 612 | } 613 | 614 | /* 615 | * modeset_draw_dev() is a new function that redraws the screen of a single 616 | * output. It takes the DRM-fd and the output devices as arguments, redraws a 617 | * new frame and schedules the page-flip for the next vsync. 618 | * 619 | * This function does the same as modeset_draw() did in the previous examples 620 | * but only for a single output device now. 621 | * After we are done rendering a frame, we have to swap the buffers. Instead of 622 | * calling drmModeSetCrtc() as we did previously, we now want to schedule this 623 | * page-flip for the next vertical-blank (vblank). We use drmModePageFlip() for 624 | * this. It takes the CRTC-id and FB-id and will asynchronously swap the buffers 625 | * when the next vblank occurs. Note that this is done by the kernel, so neither 626 | * a thread is started nor any other magic is done in libdrm. 627 | * The DRM_MODE_PAGE_FLIP_EVENT flag tells drmModePageFlip() to send us a 628 | * page-flip event on the DRM-fd when the page-flip happened. The last argument 629 | * is a data-pointer that is returned with this event. 630 | * If we wouldn't pass this flag, we would not get notified when the page-flip 631 | * happened. 632 | * 633 | * Note: If you called drmModePageFlip() and directly call it again, it will 634 | * return EBUSY if the page-flip hasn't happened in between. So you almost 635 | * always want to pass DRM_MODE_PAGE_FLIP_EVENT to get notified when the 636 | * page-flip happens so you know when to render the next frame. 637 | * If you scheduled a page-flip but call drmModeSetCrtc() before the next 638 | * vblank, then the scheduled page-flip will become a no-op. However, you will 639 | * still get notified when it happens and you still cannot call 640 | * drmModePageFlip() again until it finished. So to sum it up: there is no way 641 | * to effectively cancel a page-flip. 642 | * 643 | * If you wonder why drmModePageFlip() takes fewer arguments than 644 | * drmModeSetCrtc(), then you should take into account, that drmModePageFlip() 645 | * reuses the arguments from drmModeSetCrtc(). So things like connector-ids, 646 | * x/y-offsets and so on have to be set via drmModeSetCrtc() first before you 647 | * can use drmModePageFlip()! We do this in main() as all the previous examples 648 | * did, too. 649 | */ 650 | 651 | static void modeset_draw_dev(int fd, struct modeset_dev *dev) 652 | { 653 | struct modeset_buf *buf; 654 | unsigned int j, k, off; 655 | int ret; 656 | 657 | dev->r = next_color(&dev->r_up, dev->r, 20); 658 | dev->g = next_color(&dev->g_up, dev->g, 10); 659 | dev->b = next_color(&dev->b_up, dev->b, 5); 660 | 661 | buf = &dev->bufs[dev->front_buf ^ 1]; 662 | for (j = 0; j < buf->height; ++j) { 663 | for (k = 0; k < buf->width; ++k) { 664 | off = buf->stride * j + k * 4; 665 | *(uint32_t*)&buf->map[off] = 666 | (dev->r << 16) | (dev->g << 8) | dev->b; 667 | } 668 | } 669 | 670 | ret = drmModePageFlip(fd, dev->crtc, buf->fb, 671 | DRM_MODE_PAGE_FLIP_EVENT, dev); 672 | if (ret) { 673 | fprintf(stderr, "cannot flip CRTC for connector %u (%d): %m\n", 674 | dev->conn, errno); 675 | } else { 676 | dev->front_buf ^= 1; 677 | dev->pflip_pending = true; 678 | } 679 | } 680 | 681 | /* 682 | * modeset_cleanup() stays mostly the same. However, before resetting a CRTC to 683 | * its previous state, we wait for any outstanding page-flip to complete. This 684 | * isn't strictly neccessary, however, some DRM drivers are known to be buggy if 685 | * we call drmModeSetCrtc() if there is a pending page-flip. 686 | * Furthermore, we don't want any pending page-flips when our application exist. 687 | * Because another application might pick up the DRM device and try to schedule 688 | * their own page-flips which might then fail as long as our page-flip is 689 | * pending. 690 | * So lets be safe here and simply wait for any page-flips to complete. This is 691 | * a blocking operation, but it's mostly just <16ms so we can ignore that. 692 | */ 693 | 694 | static void modeset_cleanup(int fd) 695 | { 696 | struct modeset_dev *iter; 697 | drmEventContext ev; 698 | int ret; 699 | 700 | /* init variables */ 701 | memset(&ev, 0, sizeof(ev)); 702 | ev.version = DRM_EVENT_CONTEXT_VERSION; 703 | ev.page_flip_handler = modeset_page_flip_event; 704 | 705 | while (modeset_list) { 706 | /* remove from global list */ 707 | iter = modeset_list; 708 | modeset_list = iter->next; 709 | 710 | /* if a pageflip is pending, wait for it to complete */ 711 | iter->cleanup = true; 712 | fprintf(stderr, "wait for pending page-flip to complete...\n"); 713 | while (iter->pflip_pending) { 714 | ret = drmHandleEvent(fd, &ev); 715 | if (ret) 716 | break; 717 | } 718 | 719 | /* restore saved CRTC configuration */ 720 | if (!iter->pflip_pending) 721 | drmModeSetCrtc(fd, 722 | iter->saved_crtc->crtc_id, 723 | iter->saved_crtc->buffer_id, 724 | iter->saved_crtc->x, 725 | iter->saved_crtc->y, 726 | &iter->conn, 727 | 1, 728 | &iter->saved_crtc->mode); 729 | drmModeFreeCrtc(iter->saved_crtc); 730 | 731 | /* destroy framebuffers */ 732 | modeset_destroy_fb(fd, &iter->bufs[1]); 733 | modeset_destroy_fb(fd, &iter->bufs[0]); 734 | 735 | /* free allocated memory */ 736 | free(iter); 737 | } 738 | } 739 | 740 | /* 741 | * This example shows how to make the kernel handle page-flips and how to wait 742 | * for them in user-space. The select() example here should show you how you can 743 | * integrate these loops into your own applications without the need for a 744 | * separate modesetting thread. 745 | * 746 | * However, please note that vsync'ed double-buffering doesn't solve all 747 | * problems. Imagine that you cannot render a frame fast enough to satisfy all 748 | * vertical-blanks. In this situation, you don't want to wait after scheduling a 749 | * page-flip until the vblank happens to draw the next frame. A solution for 750 | * this is triple-buffering. It should be farily easy to extend this example to 751 | * use triple-buffering, but feel free to contact me if you have any questions 752 | * about it. 753 | * Also note that the DRM kernel API is quite limited if you want to reschedule 754 | * page-flips that haven't happened, yet. You cannot call drmModePageFlip() 755 | * twice in a single scanout-period. The behavior of drmModeSetCrtc() while a 756 | * page-flip is pending might also be unexpected. 757 | * Unfortunately, there is no ultimate solution to all modesetting problems. 758 | * This example shows the tools to do vsync'ed page-flips, however, it depends 759 | * on your use-case how you have to implement it. 760 | * 761 | * If you want more code, I can recommend reading the source-code of: 762 | * - plymouth (which uses dumb-buffers like this example; very easy to understand) 763 | * - kmscon (which uses libuterm to do this) 764 | * - wayland (very sophisticated DRM renderer; hard to understand fully as it 765 | * uses more complicated techniques like DRM planes) 766 | * - xserver (very hard to understand as it is split across many files/projects) 767 | * 768 | * Any feedback is welcome. Feel free to use this code freely for your own 769 | * documentation or projects. 770 | * 771 | * - Hosted on http://github.com/dvdhrm/docs 772 | * - Written by David Herrmann 773 | */ 774 | -------------------------------------------------------------------------------- /drm-howto/modeset.c: -------------------------------------------------------------------------------- 1 | /* 2 | * modeset - DRM Modesetting Example 3 | * 4 | * Written 2012 by David Herrmann 5 | * Dedicated to the Public Domain. 6 | */ 7 | 8 | /* 9 | * DRM Modesetting Howto 10 | * This document describes the DRM modesetting API. Before we can use the DRM 11 | * API, we have to include xf86drm.h and xf86drmMode.h. Both are provided by 12 | * libdrm which every major distribution ships by default. It has no other 13 | * dependencies and is pretty small. 14 | * 15 | * Please ignore all forward-declarations of functions which are used later. I 16 | * reordered the functions so you can read this document from top to bottom. If 17 | * you reimplement it, you would probably reorder the functions to avoid all the 18 | * nasty forward declarations. 19 | * 20 | * For easier reading, we ignore all memory-allocation errors of malloc() and 21 | * friends here. However, we try to correctly handle all other kinds of errors 22 | * that may occur. 23 | * 24 | * All functions and global variables are prefixed with "modeset_*" in this 25 | * file. So it should be clear whether a function is a local helper or if it is 26 | * provided by some external library. 27 | */ 28 | 29 | #define _GNU_SOURCE 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include 41 | #include 42 | 43 | struct modeset_dev; 44 | static int modeset_find_crtc(int fd, drmModeRes *res, drmModeConnector *conn, 45 | struct modeset_dev *dev); 46 | static int modeset_create_fb(int fd, struct modeset_dev *dev); 47 | static int modeset_setup_dev(int fd, drmModeRes *res, drmModeConnector *conn, 48 | struct modeset_dev *dev); 49 | static int modeset_open(int *out, const char *node); 50 | static int modeset_prepare(int fd); 51 | static void modeset_draw(void); 52 | static void modeset_cleanup(int fd); 53 | 54 | /* 55 | * When the linux kernel detects a graphics-card on your machine, it loads the 56 | * correct device driver (located in kernel-tree at ./drivers/gpu/drm/) and 57 | * provides two character-devices to control it. Udev (or whatever hotplugging 58 | * application you use) will create them as: 59 | * /dev/dri/card0 60 | * /dev/dri/controlID64 61 | * We only need the first one. You can hard-code this path into your application 62 | * like we do here, but it is recommended to use libudev with real hotplugging 63 | * and multi-seat support. However, this is beyond the scope of this document. 64 | * Also note that if you have multiple graphics-cards, there may also be 65 | * /dev/dri/card1, /dev/dri/card2, ... 66 | * 67 | * We simply use /dev/dri/card0 here but the user can specify another path on 68 | * the command line. 69 | * 70 | * modeset_open(out, node): This small helper function opens the DRM device 71 | * which is given as @node. The new fd is stored in @out on success. On failure, 72 | * a negative error code is returned. 73 | * After opening the file, we also check for the DRM_CAP_DUMB_BUFFER capability. 74 | * If the driver supports this capability, we can create simple memory-mapped 75 | * buffers without any driver-dependent code. As we want to avoid any radeon, 76 | * nvidia, intel, etc. specific code, we depend on DUMB_BUFFERs here. 77 | */ 78 | 79 | static int modeset_open(int *out, const char *node) 80 | { 81 | int fd, ret; 82 | uint64_t has_dumb; 83 | 84 | fd = open(node, O_RDWR | O_CLOEXEC); 85 | if (fd < 0) { 86 | ret = -errno; 87 | fprintf(stderr, "cannot open '%s': %m\n", node); 88 | return ret; 89 | } 90 | 91 | if (drmGetCap(fd, DRM_CAP_DUMB_BUFFER, &has_dumb) < 0 || 92 | !has_dumb) { 93 | fprintf(stderr, "drm device '%s' does not support dumb buffers\n", 94 | node); 95 | close(fd); 96 | return -EOPNOTSUPP; 97 | } 98 | 99 | *out = fd; 100 | return 0; 101 | } 102 | 103 | /* 104 | * As a next step we need to find our available display devices. libdrm provides 105 | * a drmModeRes structure that contains all the needed information. We can 106 | * retrieve it via drmModeGetResources(fd) and free it via 107 | * drmModeFreeResources(res) again. 108 | * 109 | * A physical connector on your graphics card is called a "connector". You can 110 | * plug a monitor into it and control what is displayed. We are definitely 111 | * interested in what connectors are currently used, so we simply iterate 112 | * through the list of connectors and try to display a test-picture on each 113 | * available monitor. 114 | * However, this isn't as easy as it sounds. First, we need to check whether the 115 | * connector is actually used (a monitor is plugged in and turned on). Then we 116 | * need to find a CRTC that can control this connector. CRTCs are described 117 | * later on. After that we create a framebuffer object. If we have all this, we 118 | * can mmap() the framebuffer and draw a test-picture into it. Then we can tell 119 | * the DRM device to show the framebuffer on the given CRTC with the selected 120 | * connector. 121 | * 122 | * As we want to draw moving pictures on the framebuffer, we actually have to 123 | * remember all these settings. Therefore, we create one "struct modeset_dev" 124 | * object for each connector+crtc+framebuffer pair that we successfully 125 | * initialized and push it into the global device-list. 126 | * 127 | * Each field of this structure is described when it is first used. But as a 128 | * summary: 129 | * "struct modeset_dev" contains: { 130 | * - @next: points to the next device in the single-linked list 131 | * 132 | * - @width: width of our buffer object 133 | * - @height: height of our buffer object 134 | * - @stride: stride value of our buffer object 135 | * - @size: size of the memory mapped buffer 136 | * - @handle: a DRM handle to the buffer object that we can draw into 137 | * - @map: pointer to the memory mapped buffer 138 | * 139 | * - @mode: the display mode that we want to use 140 | * - @fb: a framebuffer handle with our buffer object as scanout buffer 141 | * - @conn: the connector ID that we want to use with this buffer 142 | * - @crtc: the crtc ID that we want to use with this connector 143 | * - @saved_crtc: the configuration of the crtc before we changed it. We use it 144 | * so we can restore the same mode when we exit. 145 | * } 146 | */ 147 | 148 | struct modeset_dev { 149 | struct modeset_dev *next; 150 | 151 | uint32_t width; 152 | uint32_t height; 153 | uint32_t stride; 154 | uint32_t size; 155 | uint32_t handle; 156 | uint8_t *map; 157 | 158 | drmModeModeInfo mode; 159 | uint32_t fb; 160 | uint32_t conn; 161 | uint32_t crtc; 162 | drmModeCrtc *saved_crtc; 163 | }; 164 | 165 | static struct modeset_dev *modeset_list = NULL; 166 | 167 | /* 168 | * So as next step we need to actually prepare all connectors that we find. We 169 | * do this in this little helper function: 170 | * 171 | * modeset_prepare(fd): This helper function takes the DRM fd as argument and 172 | * then simply retrieves the resource-info from the device. It then iterates 173 | * through all connectors and calls other helper functions to initialize this 174 | * connector (described later on). 175 | * If the initialization was successful, we simply add this object as new device 176 | * into the global modeset device list. 177 | * 178 | * The resource-structure contains a list of all connector-IDs. We use the 179 | * helper function drmModeGetConnector() to retrieve more information on each 180 | * connector. After we are done with it, we free it again with 181 | * drmModeFreeConnector(). 182 | * Our helper modeset_setup_dev() returns -ENOENT if the connector is currently 183 | * unused and no monitor is plugged in. So we can ignore this connector. 184 | */ 185 | 186 | static int modeset_prepare(int fd) 187 | { 188 | drmModeRes *res; 189 | drmModeConnector *conn; 190 | int i; 191 | struct modeset_dev *dev; 192 | int ret; 193 | 194 | /* retrieve resources */ 195 | res = drmModeGetResources(fd); 196 | if (!res) { 197 | fprintf(stderr, "cannot retrieve DRM resources (%d): %m\n", 198 | errno); 199 | return -errno; 200 | } 201 | 202 | /* iterate all connectors */ 203 | for (i = 0; i < res->count_connectors; ++i) { 204 | /* get information for each connector */ 205 | conn = drmModeGetConnector(fd, res->connectors[i]); 206 | if (!conn) { 207 | fprintf(stderr, "cannot retrieve DRM connector %u:%u (%d): %m\n", 208 | i, res->connectors[i], errno); 209 | continue; 210 | } 211 | 212 | /* create a device structure */ 213 | dev = malloc(sizeof(*dev)); 214 | memset(dev, 0, sizeof(*dev)); 215 | dev->conn = conn->connector_id; 216 | 217 | /* call helper function to prepare this connector */ 218 | ret = modeset_setup_dev(fd, res, conn, dev); 219 | if (ret) { 220 | if (ret != -ENOENT) { 221 | errno = -ret; 222 | fprintf(stderr, "cannot setup device for connector %u:%u (%d): %m\n", 223 | i, res->connectors[i], errno); 224 | } 225 | free(dev); 226 | drmModeFreeConnector(conn); 227 | continue; 228 | } 229 | 230 | /* free connector data and link device into global list */ 231 | drmModeFreeConnector(conn); 232 | dev->next = modeset_list; 233 | modeset_list = dev; 234 | } 235 | 236 | /* free resources again */ 237 | drmModeFreeResources(res); 238 | return 0; 239 | } 240 | 241 | /* 242 | * Now we dig deeper into setting up a single connector. As described earlier, 243 | * we need to check several things first: 244 | * * If the connector is currently unused, that is, no monitor is plugged in, 245 | * then we can ignore it. 246 | * * We have to find a suitable resolution and refresh-rate. All this is 247 | * available in drmModeModeInfo structures saved for each crtc. We simply 248 | * use the first mode that is available. This is always the mode with the 249 | * highest resolution. 250 | * A more sophisticated mode-selection should be done in real applications, 251 | * though. 252 | * * Then we need to find an CRTC that can drive this connector. A CRTC is an 253 | * internal resource of each graphics-card. The number of CRTCs controls how 254 | * many connectors can be controlled indepedently. That is, a graphics-cards 255 | * may have more connectors than CRTCs, which means, not all monitors can be 256 | * controlled independently. 257 | * There is actually the possibility to control multiple connectors via a 258 | * single CRTC if the monitors should display the same content. However, we 259 | * do not make use of this here. 260 | * So think of connectors as pipelines to the connected monitors and the 261 | * CRTCs are the controllers that manage which data goes to which pipeline. 262 | * If there are more pipelines than CRTCs, then we cannot control all of 263 | * them at the same time. 264 | * * We need to create a framebuffer for this connector. A framebuffer is a 265 | * memory buffer that we can write XRGB32 data into. So we use this to 266 | * render our graphics and then the CRTC can scan-out this data from the 267 | * framebuffer onto the monitor. 268 | */ 269 | 270 | static int modeset_setup_dev(int fd, drmModeRes *res, drmModeConnector *conn, 271 | struct modeset_dev *dev) 272 | { 273 | int ret; 274 | 275 | /* check if a monitor is connected */ 276 | if (conn->connection != DRM_MODE_CONNECTED) { 277 | fprintf(stderr, "ignoring unused connector %u\n", 278 | conn->connector_id); 279 | return -ENOENT; 280 | } 281 | 282 | /* check if there is at least one valid mode */ 283 | if (conn->count_modes == 0) { 284 | fprintf(stderr, "no valid mode for connector %u\n", 285 | conn->connector_id); 286 | return -EFAULT; 287 | } 288 | 289 | /* copy the mode information into our device structure */ 290 | memcpy(&dev->mode, &conn->modes[0], sizeof(dev->mode)); 291 | dev->width = conn->modes[0].hdisplay; 292 | dev->height = conn->modes[0].vdisplay; 293 | fprintf(stderr, "mode for connector %u is %ux%u\n", 294 | conn->connector_id, dev->width, dev->height); 295 | 296 | /* find a crtc for this connector */ 297 | ret = modeset_find_crtc(fd, res, conn, dev); 298 | if (ret) { 299 | fprintf(stderr, "no valid crtc for connector %u\n", 300 | conn->connector_id); 301 | return ret; 302 | } 303 | 304 | /* create a framebuffer for this CRTC */ 305 | ret = modeset_create_fb(fd, dev); 306 | if (ret) { 307 | fprintf(stderr, "cannot create framebuffer for connector %u\n", 308 | conn->connector_id); 309 | return ret; 310 | } 311 | 312 | return 0; 313 | } 314 | 315 | /* 316 | * modeset_find_crtc(fd, res, conn, dev): This small helper tries to find a 317 | * suitable CRTC for the given connector. We have actually have to introduce one 318 | * more DRM object to make this more clear: Encoders. 319 | * Encoders help the CRTC to convert data from a framebuffer into the right 320 | * format that can be used for the chosen connector. We do not have to 321 | * understand any more of these conversions to make use of it. However, you must 322 | * know that each connector has a limited list of encoders that it can use. And 323 | * each encoder can only work with a limited list of CRTCs. So what we do is 324 | * trying each encoder that is available and looking for a CRTC that this 325 | * encoder can work with. If we find the first working combination, we are happy 326 | * and write it into the @dev structure. 327 | * But before iterating all available encoders, we first try the currently 328 | * active encoder+crtc on a connector to avoid a full modeset. 329 | * 330 | * However, before we can use a CRTC we must make sure that no other device, 331 | * that we setup previously, is already using this CRTC. Remember, we can only 332 | * drive one connector per CRTC! So we simply iterate through the "modeset_list" 333 | * of previously setup devices and check that this CRTC wasn't used before. 334 | * Otherwise, we continue with the next CRTC/Encoder combination. 335 | */ 336 | 337 | static int modeset_find_crtc(int fd, drmModeRes *res, drmModeConnector *conn, 338 | struct modeset_dev *dev) 339 | { 340 | drmModeEncoder *enc; 341 | int i, j; 342 | uint32_t crtc; 343 | struct modeset_dev *iter; 344 | 345 | /* first try the currently conected encoder+crtc */ 346 | if (conn->encoder_id) 347 | enc = drmModeGetEncoder(fd, conn->encoder_id); 348 | else 349 | enc = NULL; 350 | 351 | if (enc) { 352 | if (enc->crtc_id) { 353 | crtc = enc->crtc_id; 354 | for (iter = modeset_list; iter; iter = iter->next) { 355 | if (iter->crtc == crtc) { 356 | crtc = -1; 357 | break; 358 | } 359 | } 360 | 361 | if (crtc >= 0) { 362 | drmModeFreeEncoder(enc); 363 | dev->crtc = crtc; 364 | return 0; 365 | } 366 | } 367 | 368 | drmModeFreeEncoder(enc); 369 | } 370 | 371 | /* If the connector is not currently bound to an encoder or if the 372 | * encoder+crtc is already used by another connector (actually unlikely 373 | * but lets be safe), iterate all other available encoders to find a 374 | * matching CRTC. */ 375 | for (i = 0; i < conn->count_encoders; ++i) { 376 | enc = drmModeGetEncoder(fd, conn->encoders[i]); 377 | if (!enc) { 378 | fprintf(stderr, "cannot retrieve encoder %u:%u (%d): %m\n", 379 | i, conn->encoders[i], errno); 380 | continue; 381 | } 382 | 383 | /* iterate all global CRTCs */ 384 | for (j = 0; j < res->count_crtcs; ++j) { 385 | /* check whether this CRTC works with the encoder */ 386 | if (!(enc->possible_crtcs & (1 << j))) 387 | continue; 388 | 389 | /* check that no other device already uses this CRTC */ 390 | crtc = res->crtcs[j]; 391 | for (iter = modeset_list; iter; iter = iter->next) { 392 | if (iter->crtc == crtc) { 393 | crtc = -1; 394 | break; 395 | } 396 | } 397 | 398 | /* we have found a CRTC, so save it and return */ 399 | if (crtc >= 0) { 400 | drmModeFreeEncoder(enc); 401 | dev->crtc = crtc; 402 | return 0; 403 | } 404 | } 405 | 406 | drmModeFreeEncoder(enc); 407 | } 408 | 409 | fprintf(stderr, "cannot find suitable CRTC for connector %u\n", 410 | conn->connector_id); 411 | return -ENOENT; 412 | } 413 | 414 | /* 415 | * modeset_create_fb(fd, dev): After we have found a crtc+connector+mode 416 | * combination, we need to actually create a suitable framebuffer that we can 417 | * use with it. There are actually two ways to do that: 418 | * * We can create a so called "dumb buffer". This is a buffer that we can 419 | * memory-map via mmap() and every driver supports this. We can use it for 420 | * unaccelerated software rendering on the CPU. 421 | * * We can use libgbm to create buffers available for hardware-acceleration. 422 | * libgbm is an abstraction layer that creates these buffers for each 423 | * available DRM driver. As there is no generic API for this, each driver 424 | * provides its own way to create these buffers. 425 | * We can then use such buffers to create OpenGL contexts with the mesa3D 426 | * library. 427 | * We use the first solution here as it is much simpler and doesn't require any 428 | * external libraries. However, if you want to use hardware-acceleration via 429 | * OpenGL, it is actually pretty easy to create such buffers with libgbm and 430 | * libEGL. But this is beyond the scope of this document. 431 | * 432 | * So what we do is requesting a new dumb-buffer from the driver. We specify the 433 | * same size as the current mode that we selected for the connector. 434 | * Then we request the driver to prepare this buffer for memory mapping. After 435 | * that we perform the actual mmap() call. So we can now access the framebuffer 436 | * memory directly via the dev->map memory map. 437 | */ 438 | 439 | static int modeset_create_fb(int fd, struct modeset_dev *dev) 440 | { 441 | struct drm_mode_create_dumb creq; 442 | struct drm_mode_destroy_dumb dreq; 443 | struct drm_mode_map_dumb mreq; 444 | int ret; 445 | 446 | /* create dumb buffer */ 447 | memset(&creq, 0, sizeof(creq)); 448 | creq.width = dev->width; 449 | creq.height = dev->height; 450 | creq.bpp = 32; 451 | ret = drmIoctl(fd, DRM_IOCTL_MODE_CREATE_DUMB, &creq); 452 | if (ret < 0) { 453 | fprintf(stderr, "cannot create dumb buffer (%d): %m\n", 454 | errno); 455 | return -errno; 456 | } 457 | dev->stride = creq.pitch; 458 | dev->size = creq.size; 459 | dev->handle = creq.handle; 460 | 461 | /* create framebuffer object for the dumb-buffer */ 462 | ret = drmModeAddFB(fd, dev->width, dev->height, 24, 32, dev->stride, 463 | dev->handle, &dev->fb); 464 | if (ret) { 465 | fprintf(stderr, "cannot create framebuffer (%d): %m\n", 466 | errno); 467 | ret = -errno; 468 | goto err_destroy; 469 | } 470 | 471 | /* prepare buffer for memory mapping */ 472 | memset(&mreq, 0, sizeof(mreq)); 473 | mreq.handle = dev->handle; 474 | ret = drmIoctl(fd, DRM_IOCTL_MODE_MAP_DUMB, &mreq); 475 | if (ret) { 476 | fprintf(stderr, "cannot map dumb buffer (%d): %m\n", 477 | errno); 478 | ret = -errno; 479 | goto err_fb; 480 | } 481 | 482 | /* perform actual memory mapping */ 483 | dev->map = mmap(0, dev->size, PROT_READ | PROT_WRITE, MAP_SHARED, 484 | fd, mreq.offset); 485 | if (dev->map == MAP_FAILED) { 486 | fprintf(stderr, "cannot mmap dumb buffer (%d): %m\n", 487 | errno); 488 | ret = -errno; 489 | goto err_fb; 490 | } 491 | 492 | /* clear the framebuffer to 0 */ 493 | memset(dev->map, 0, dev->size); 494 | 495 | return 0; 496 | 497 | err_fb: 498 | drmModeRmFB(fd, dev->fb); 499 | err_destroy: 500 | memset(&dreq, 0, sizeof(dreq)); 501 | dreq.handle = dev->handle; 502 | drmIoctl(fd, DRM_IOCTL_MODE_DESTROY_DUMB, &dreq); 503 | return ret; 504 | } 505 | 506 | /* 507 | * Finally! We have a connector with a suitable CRTC. We know which mode we want 508 | * to use and we have a framebuffer of the correct size that we can write to. 509 | * There is nothing special left to do. We only have to program the CRTC to 510 | * connect each new framebuffer to each selected connector for each combination 511 | * that we saved in the global modeset_list. 512 | * This is done with a call to drmModeSetCrtc(). 513 | * 514 | * So we are ready for our main() function. First we check whether the user 515 | * specified a DRM device on the command line, otherwise we use the default 516 | * /dev/dri/card0. Then we open the device via modeset_open(). modeset_prepare() 517 | * prepares all connectors and we can loop over "modeset_list" and call 518 | * drmModeSetCrtc() on every CRTC/connector combination. 519 | * 520 | * But printing empty black pages is boring so we have another helper function 521 | * modeset_draw() that draws some colors into the framebuffer for 5 seconds and 522 | * then returns. And then we have all the cleanup functions which correctly free 523 | * all devices again after we used them. All these functions are described below 524 | * the main() function. 525 | * 526 | * As a side note: drmModeSetCrtc() actually takes a list of connectors that we 527 | * want to control with this CRTC. We pass only one connector, though. As 528 | * explained earlier, if we used multiple connectors, then all connectors would 529 | * have the same controlling framebuffer so the output would be cloned. This is 530 | * most often not what you want so we avoid explaining this feature here. 531 | * Furthermore, all connectors will have to run with the same mode, which is 532 | * also often not guaranteed. So instead, we only use one connector per CRTC. 533 | * 534 | * Before calling drmModeSetCrtc() we also save the current CRTC configuration. 535 | * This is used in modeset_cleanup() to restore the CRTC to the same mode as was 536 | * before we changed it. 537 | * If we don't do this, the screen will stay blank after we exit until another 538 | * application performs modesetting itself. 539 | */ 540 | 541 | int main(int argc, char **argv) 542 | { 543 | int ret, fd; 544 | const char *card; 545 | struct modeset_dev *iter; 546 | 547 | /* check which DRM device to open */ 548 | if (argc > 1) 549 | card = argv[1]; 550 | else 551 | card = "/dev/dri/card0"; 552 | 553 | fprintf(stderr, "using card '%s'\n", card); 554 | 555 | /* open the DRM device */ 556 | ret = modeset_open(&fd, card); 557 | if (ret) 558 | goto out_return; 559 | 560 | /* prepare all connectors and CRTCs */ 561 | ret = modeset_prepare(fd); 562 | if (ret) 563 | goto out_close; 564 | 565 | /* perform actual modesetting on each found connector+CRTC */ 566 | for (iter = modeset_list; iter; iter = iter->next) { 567 | iter->saved_crtc = drmModeGetCrtc(fd, iter->crtc); 568 | ret = drmModeSetCrtc(fd, iter->crtc, iter->fb, 0, 0, 569 | &iter->conn, 1, &iter->mode); 570 | if (ret) 571 | fprintf(stderr, "cannot set CRTC for connector %u (%d): %m\n", 572 | iter->conn, errno); 573 | } 574 | 575 | /* draw some colors for 5seconds */ 576 | modeset_draw(); 577 | 578 | /* cleanup everything */ 579 | modeset_cleanup(fd); 580 | 581 | ret = 0; 582 | 583 | out_close: 584 | close(fd); 585 | out_return: 586 | if (ret) { 587 | errno = -ret; 588 | fprintf(stderr, "modeset failed with error %d: %m\n", errno); 589 | } else { 590 | fprintf(stderr, "exiting\n"); 591 | } 592 | return ret; 593 | } 594 | 595 | /* 596 | * A short helper function to compute a changing color value. No need to 597 | * understand it. 598 | */ 599 | 600 | static uint8_t next_color(bool *up, uint8_t cur, unsigned int mod) 601 | { 602 | uint8_t next; 603 | 604 | next = cur + (*up ? 1 : -1) * (rand() % mod); 605 | if ((*up && next < cur) || (!*up && next > cur)) { 606 | *up = !*up; 607 | next = cur; 608 | } 609 | 610 | return next; 611 | } 612 | 613 | /* 614 | * modeset_draw(): This draws a solid color into all configured framebuffers. 615 | * Every 100ms the color changes to a slightly different color so we get some 616 | * kind of smoothly changing color-gradient. 617 | * 618 | * The color calculation can be ignored as it is pretty boring. So the 619 | * interesting stuff is iterating over "modeset_list" and then through all lines 620 | * and width. We then set each pixel individually to the current color. 621 | * 622 | * We do this 50 times as we sleep 100ms after each redraw round. This makes 623 | * 50*100ms = 5000ms = 5s so it takes about 5seconds to finish this loop. 624 | * 625 | * Please note that we draw directly into the framebuffer. This means that you 626 | * will see flickering as the monitor might refresh while we redraw the screen. 627 | * To avoid this you would need to use two framebuffers and a call to 628 | * drmModeSetCrtc() to switch between both buffers. 629 | * You can also use drmModePageFlip() to do a vsync'ed pageflip. But this is 630 | * beyond the scope of this document. 631 | */ 632 | 633 | static void modeset_draw(void) 634 | { 635 | uint8_t r, g, b; 636 | bool r_up, g_up, b_up; 637 | unsigned int i, j, k, off; 638 | struct modeset_dev *iter; 639 | 640 | srand(time(NULL)); 641 | r = rand() % 0xff; 642 | g = rand() % 0xff; 643 | b = rand() % 0xff; 644 | r_up = g_up = b_up = true; 645 | 646 | for (i = 0; i < 50; ++i) { 647 | r = next_color(&r_up, r, 20); 648 | g = next_color(&g_up, g, 10); 649 | b = next_color(&b_up, b, 5); 650 | 651 | for (iter = modeset_list; iter; iter = iter->next) { 652 | for (j = 0; j < iter->height; ++j) { 653 | for (k = 0; k < iter->width; ++k) { 654 | off = iter->stride * j + k * 4; 655 | *(uint32_t*)&iter->map[off] = 656 | (r << 16) | (g << 8) | b; 657 | } 658 | } 659 | } 660 | 661 | usleep(100000); 662 | } 663 | } 664 | 665 | /* 666 | * modeset_cleanup(fd): This cleans up all the devices we created during 667 | * modeset_prepare(). It resets the CRTCs to their saved states and deallocates 668 | * all memory. 669 | * It should be pretty obvious how all of this works. 670 | */ 671 | 672 | static void modeset_cleanup(int fd) 673 | { 674 | struct modeset_dev *iter; 675 | struct drm_mode_destroy_dumb dreq; 676 | 677 | while (modeset_list) { 678 | /* remove from global list */ 679 | iter = modeset_list; 680 | modeset_list = iter->next; 681 | 682 | /* restore saved CRTC configuration */ 683 | drmModeSetCrtc(fd, 684 | iter->saved_crtc->crtc_id, 685 | iter->saved_crtc->buffer_id, 686 | iter->saved_crtc->x, 687 | iter->saved_crtc->y, 688 | &iter->conn, 689 | 1, 690 | &iter->saved_crtc->mode); 691 | drmModeFreeCrtc(iter->saved_crtc); 692 | 693 | /* unmap buffer */ 694 | munmap(iter->map, iter->size); 695 | 696 | /* delete framebuffer */ 697 | drmModeRmFB(fd, iter->fb); 698 | 699 | /* delete dumb buffer */ 700 | memset(&dreq, 0, sizeof(dreq)); 701 | dreq.handle = iter->handle; 702 | drmIoctl(fd, DRM_IOCTL_MODE_DESTROY_DUMB, &dreq); 703 | 704 | /* free allocated memory */ 705 | free(iter); 706 | } 707 | } 708 | 709 | /* 710 | * I hope this was a short but easy overview of the DRM modesetting API. The DRM 711 | * API offers much more capabilities including: 712 | * - double-buffering or tripple-buffering (or whatever you want) 713 | * - vsync'ed page-flips 714 | * - hardware-accelerated rendering (for example via OpenGL) 715 | * - output cloning 716 | * - graphics-clients plus authentication 717 | * - DRM planes/overlays/sprites 718 | * - ... 719 | * If you are interested in these topics, I can currently only redirect you to 720 | * existing implementations, including: 721 | * - plymouth (which uses dumb-buffers like this example; very easy to understand) 722 | * - kmscon (which uses libuterm to do this) 723 | * - wayland (very sophisticated DRM renderer; hard to understand fully as it 724 | * uses more complicated techniques like DRM planes) 725 | * - xserver (very hard to understand as it is split across many files/projects) 726 | * 727 | * But understanding how modesetting (as described in this document) works, is 728 | * essential to understand all further DRM topics. 729 | * 730 | * Any feedback is welcome. Feel free to use this code freely for your own 731 | * documentation or projects. 732 | * 733 | * - Hosted on http://github.com/dvdhrm/docs 734 | * - Written by David Herrmann 735 | */ 736 | --------------------------------------------------------------------------------