Define _GNU_SOURCE for O_CLOEXEC flag
[profile/ivi/weston.git] / compositor / compositor-drm.c
1 /*
2  * Copyright © 2008-2010 Kristian Høgsberg
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation; either version 2 of the License, or
7  * (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software Foundation,
16  * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
17  */
18
19 #define _GNU_SOURCE
20
21 #include <stdio.h>
22 #include <stdlib.h>
23 #include <string.h>
24 #include <fcntl.h>
25 #include <unistd.h>
26
27 #include <xf86drm.h>
28 #include <xf86drmMode.h>
29
30 #define GL_GLEXT_PROTOTYPES
31 #define EGL_EGLEXT_PROTOTYPES
32 #include <GLES2/gl2.h>
33 #include <GLES2/gl2ext.h>
34 #include <EGL/egl.h>
35 #include <EGL/eglext.h>
36
37 #include "compositor.h"
38
39 struct drm_compositor {
40         struct wlsc_compositor base;
41
42         struct udev *udev;
43         struct wl_event_source *drm_source;
44
45         struct udev_monitor *udev_monitor;
46         struct wl_event_source *udev_drm_source;
47
48         struct {
49                 int fd;
50         } drm;
51         uint32_t crtc_allocator;
52         uint32_t connector_allocator;
53         struct tty *tty;
54 };
55
56 struct drm_output {
57         struct wlsc_output   base;
58
59         drmModeModeInfo mode;
60         uint32_t crtc_id;
61         uint32_t connector_id;
62         GLuint rbo[2];
63         uint32_t fb_id[2];
64         EGLImageKHR image[2];
65         uint32_t current;       
66 };
67
68 static int
69 drm_output_prepare_render(struct wlsc_output *output_base)
70 {
71         struct drm_output *output = (struct drm_output *) output_base;
72
73         glFramebufferRenderbuffer(GL_FRAMEBUFFER,
74                                   GL_COLOR_ATTACHMENT0,
75                                   GL_RENDERBUFFER,
76                                   output->rbo[output->current]);
77
78         if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
79                 return -1;
80
81         return 0;
82 }
83
84 static int
85 drm_output_present(struct wlsc_output *output_base)
86 {
87         struct drm_output *output = (struct drm_output *) output_base;
88         struct drm_compositor *c =
89                 (struct drm_compositor *) output->base.compositor;
90
91         if (drm_output_prepare_render(&output->base))
92                 return -1;
93         glFlush();
94
95         output->current ^= 1;
96
97         drmModePageFlip(c->drm.fd, output->crtc_id,
98                         output->fb_id[output->current ^ 1],
99                         DRM_MODE_PAGE_FLIP_EVENT, output);
100
101         return 0;
102 }
103
104 static void
105 page_flip_handler(int fd, unsigned int frame,
106                   unsigned int sec, unsigned int usec, void *data)
107 {
108         struct wlsc_output *output = data;
109         uint32_t msecs;
110
111         msecs = sec * 1000 + usec / 1000;
112         wlsc_output_finish_frame(output, msecs);
113 }
114
115 static void
116 on_drm_input(int fd, uint32_t mask, void *data)
117 {
118         drmEventContext evctx;
119
120         memset(&evctx, 0, sizeof evctx);
121         evctx.version = DRM_EVENT_CONTEXT_VERSION;
122         evctx.page_flip_handler = page_flip_handler;
123         drmHandleEvent(fd, &evctx);
124 }
125
126 static int
127 init_egl(struct drm_compositor *ec, struct udev_device *device)
128 {
129         EGLint major, minor;
130         const char *extensions, *filename;
131         int fd;
132         static const EGLint context_attribs[] = {
133                 EGL_CONTEXT_CLIENT_VERSION, 2,
134                 EGL_NONE
135         };
136
137         filename = udev_device_get_devnode(device);
138         fd = open(filename, O_RDWR, O_CLOEXEC);
139         if (fd < 0) {
140                 /* Probably permissions error */
141                 fprintf(stderr, "couldn't open %s, skipping\n",
142                         udev_device_get_devnode(device));
143                 return -1;
144         }
145
146         ec->drm.fd = fd;
147         ec->base.display = eglGetDRMDisplayMESA(ec->drm.fd);
148         if (ec->base.display == NULL) {
149                 fprintf(stderr, "failed to create display\n");
150                 return -1;
151         }
152
153         if (!eglInitialize(ec->base.display, &major, &minor)) {
154                 fprintf(stderr, "failed to initialize display\n");
155                 return -1;
156         }
157
158         extensions = eglQueryString(ec->base.display, EGL_EXTENSIONS);
159         if (!strstr(extensions, "EGL_KHR_surfaceless_opengl")) {
160                 fprintf(stderr, "EGL_KHR_surfaceless_opengl not available\n");
161                 return -1;
162         }
163
164         if (!eglBindAPI(EGL_OPENGL_ES_API)) {
165                 fprintf(stderr, "failed to bind api EGL_OPENGL_ES_API\n");
166                 return -1;
167         }
168
169         ec->base.context = eglCreateContext(ec->base.display, NULL,
170                                             EGL_NO_CONTEXT, context_attribs);
171         if (ec->base.context == NULL) {
172                 fprintf(stderr, "failed to create context\n");
173                 return -1;
174         }
175
176         if (!eglMakeCurrent(ec->base.display, EGL_NO_SURFACE,
177                             EGL_NO_SURFACE, ec->base.context)) {
178                 fprintf(stderr, "failed to make context current\n");
179                 return -1;
180         }
181
182         return 0;
183 }
184
185 static drmModeModeInfo builtin_1024x768 = {
186         63500,                  /* clock */
187         1024, 1072, 1176, 1328, 0,
188         768, 771, 775, 798, 0,
189         59920,
190         DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_PVSYNC,
191         0,
192         "1024x768"
193 };
194
195 static int
196 create_output_for_connector(struct drm_compositor *ec,
197                             drmModeRes *resources,
198                             drmModeConnector *connector,
199                             int x, int y)
200 {
201         struct drm_output *output;
202         drmModeEncoder *encoder;
203         drmModeModeInfo *mode;
204         int i, ret;
205         EGLint handle, stride, attribs[] = {
206                 EGL_WIDTH,              0,
207                 EGL_HEIGHT,             0,
208                 EGL_DRM_BUFFER_FORMAT_MESA,     EGL_DRM_BUFFER_FORMAT_ARGB32_MESA,
209                 EGL_DRM_BUFFER_USE_MESA,        EGL_DRM_BUFFER_USE_SCANOUT_MESA,
210                 EGL_NONE
211         };
212
213         output = malloc(sizeof *output);
214         if (output == NULL)
215                 return -1;
216
217         if (connector->count_modes > 0) 
218                 mode = &connector->modes[0];
219         else
220                 mode = &builtin_1024x768;
221
222         encoder = drmModeGetEncoder(ec->drm.fd, connector->encoders[0]);
223         if (encoder == NULL) {
224                 fprintf(stderr, "No encoder for connector.\n");
225                 return -1;
226         }
227
228         for (i = 0; i < resources->count_crtcs; i++) {
229                 if (encoder->possible_crtcs & (1 << i) &&
230                     !(ec->crtc_allocator & (1 << resources->crtcs[i])))
231                         break;
232         }
233         if (i == resources->count_crtcs) {
234                 fprintf(stderr, "No usable crtc for encoder.\n");
235                 return -1;
236         }
237
238         memset(output, 0, sizeof *output);
239         wlsc_output_init(&output->base, &ec->base, x, y,
240                          mode->hdisplay, mode->vdisplay, 0);
241
242         output->crtc_id = resources->crtcs[i];
243         ec->crtc_allocator |= (1 << output->crtc_id);
244
245         output->connector_id = connector->connector_id;
246         ec->connector_allocator |= (1 << output->connector_id);
247         output->mode = *mode;
248
249         drmModeFreeEncoder(encoder);
250
251         glGenRenderbuffers(2, output->rbo);
252         for (i = 0; i < 2; i++) {
253                 glBindRenderbuffer(GL_RENDERBUFFER, output->rbo[i]);
254
255                 attribs[1] = output->base.width;
256                 attribs[3] = output->base.height;
257                 output->image[i] =
258                         eglCreateDRMImageMESA(ec->base.display, attribs);
259                 glEGLImageTargetRenderbufferStorageOES(GL_RENDERBUFFER,
260                                                        output->image[i]);
261                 eglExportDRMImageMESA(ec->base.display, output->image[i],
262                                       NULL, &handle, &stride);
263
264                 ret = drmModeAddFB(ec->drm.fd,
265                                    output->base.width, output->base.height,
266                                    32, 32, stride, handle, &output->fb_id[i]);
267                 if (ret) {
268                         fprintf(stderr, "failed to add fb %d: %m\n", i);
269                         return -1;
270                 }
271         }
272
273         output->current = 0;
274         glFramebufferRenderbuffer(GL_FRAMEBUFFER,
275                                   GL_COLOR_ATTACHMENT0,
276                                   GL_RENDERBUFFER,
277                                   output->rbo[output->current]);
278         ret = drmModeSetCrtc(ec->drm.fd, output->crtc_id,
279                              output->fb_id[output->current ^ 1], 0, 0,
280                              &output->connector_id, 1, &output->mode);
281         if (ret) {
282                 fprintf(stderr, "failed to set mode: %m\n");
283                 return -1;
284         }
285
286         output->base.prepare_render = drm_output_prepare_render;
287         output->base.present = drm_output_present;
288
289         wl_list_insert(ec->base.output_list.prev, &output->base.link);
290
291         return 0;
292 }
293
294 static int
295 create_outputs(struct drm_compositor *ec, int option_connector)
296 {
297         drmModeConnector *connector;
298         drmModeRes *resources;
299         int i;
300         int x = 0, y = 0;
301
302         resources = drmModeGetResources(ec->drm.fd);
303         if (!resources) {
304                 fprintf(stderr, "drmModeGetResources failed\n");
305                 return -1;
306         }
307
308         for (i = 0; i < resources->count_connectors; i++) {
309                 connector = drmModeGetConnector(ec->drm.fd, resources->connectors[i]);
310                 if (connector == NULL)
311                         continue;
312
313                 if (connector->connection == DRM_MODE_CONNECTED &&
314                     (option_connector == 0 ||
315                      connector->connector_id == option_connector))
316                         if (create_output_for_connector(ec, resources,
317                                                         connector, x, y) < 0)
318                                 return -1;
319
320                 x += container_of(ec->base.output_list.prev, struct wlsc_output,
321                                   link)->width;
322
323                 drmModeFreeConnector(connector);
324         }
325
326         if (wl_list_empty(&ec->base.output_list)) {
327                 fprintf(stderr, "No currently active connector found.\n");
328                 return -1;
329         }
330
331         drmModeFreeResources(resources);
332
333         return 0;
334 }
335
336 static int
337 destroy_output(struct drm_output *output)
338 {
339         struct drm_compositor *ec =
340                 (struct drm_compositor *) output->base.compositor;
341         int i;
342
343         glFramebufferRenderbuffer(GL_FRAMEBUFFER,
344                                   GL_COLOR_ATTACHMENT0,
345                                   GL_RENDERBUFFER,
346                                   0);
347
348         glBindRenderbuffer(GL_RENDERBUFFER, 0);
349         glDeleteRenderbuffers(2, output->rbo);
350
351         for (i = 0; i < 2; i++) {
352                 eglDestroyImageKHR(ec->base.display, output->image[i]);
353                 drmModeRmFB(ec->drm.fd, output->fb_id[i]);
354         }
355         
356         ec->crtc_allocator &= ~(1 << output->crtc_id);
357         ec->connector_allocator &= ~(1 << output->connector_id);
358
359         wlsc_output_destroy(&output->base);
360         wl_list_remove(&output->base.link);
361
362         free(output);
363
364         return 0;
365 }
366
367 static void
368 update_outputs(struct drm_compositor *ec)
369 {
370         drmModeConnector *connector;
371         drmModeRes *resources;
372         struct drm_output *output, *next;
373         int x = 0, y = 0;
374         int x_offset = 0, y_offset = 0;
375         uint32_t connected = 0, disconnects = 0;
376         int i;
377
378         resources = drmModeGetResources(ec->drm.fd);
379         if (!resources) {
380                 fprintf(stderr, "drmModeGetResources failed\n");
381                 return;
382         }
383
384         /* collect new connects */
385         for (i = 0; i < resources->count_connectors; i++) {
386                 connector =
387                         drmModeGetConnector(ec->drm.fd,
388                                             resources->connectors[i]);
389                 if (connector == NULL ||
390                     connector->connection != DRM_MODE_CONNECTED)
391                         continue;
392
393                 connected |= (1 << connector->connector_id);
394                 
395                 if (!(ec->connector_allocator & (1 << connector->connector_id))) {
396                         struct wlsc_output *last_output =
397                                 container_of(ec->base.output_list.prev,
398                                              struct wlsc_output, link);
399
400                         /* XXX: not yet needed, we die with 0 outputs */
401                         if (!wl_list_empty(&ec->base.output_list))
402                                 x = last_output->x + last_output->width;
403                         else
404                                 x = 0;
405                         y = 0;
406                         create_output_for_connector(ec, resources,
407                                                     connector, x, y);
408                         printf("connector %d connected\n",
409                                connector->connector_id);
410                                 
411                 }
412                 drmModeFreeConnector(connector);
413         }
414         drmModeFreeResources(resources);
415
416         disconnects = ec->connector_allocator & ~connected;
417         if (disconnects) {
418                 wl_list_for_each_safe(output, next, &ec->base.output_list,
419                                       base.link) {
420                         if (x_offset != 0 || y_offset != 0) {
421                                 wlsc_output_move(&output->base,
422                                                  output->base.x - x_offset,
423                                                  output->base.y - y_offset);
424                         }
425
426                         if (disconnects & (1 << output->connector_id)) {
427                                 disconnects &= ~(1 << output->connector_id);
428                                 printf("connector %d disconnected\n",
429                                        output->connector_id);
430                                 x_offset += output->base.width;
431                                 destroy_output(output);
432                         }
433                 }
434         }
435
436         /* FIXME: handle zero outputs, without terminating */   
437         if (ec->connector_allocator == 0)
438                 wl_display_terminate(ec->base.wl_display);
439 }
440
441 static int
442 udev_event_is_hotplug(struct udev_device *device)
443 {
444         struct udev_list_entry *list, *hotplug_entry;
445         
446         list = udev_device_get_properties_list_entry(device);
447
448         hotplug_entry = udev_list_entry_get_by_name(list, "HOTPLUG");
449         if (hotplug_entry == NULL)
450                 return 0;
451
452         return strcmp(udev_list_entry_get_value(hotplug_entry), "1") == 0;
453 }
454
455 static void
456 udev_drm_event(int fd, uint32_t mask, void *data)
457 {
458         struct drm_compositor *ec = data;
459         struct udev_device *event;
460
461         event = udev_monitor_receive_device(ec->udev_monitor);
462         
463         if (udev_event_is_hotplug(event))
464                 update_outputs(ec);
465
466         udev_device_unref(event);
467 }
468
469 static void
470 drm_destroy(struct wlsc_compositor *ec)
471 {
472         struct drm_compositor *d = (struct drm_compositor *) ec;
473
474         tty_destroy(d->tty);
475
476         free(d);
477 }
478
479 struct wlsc_compositor *
480 drm_compositor_create(struct wl_display *display, int connector)
481 {
482         struct drm_compositor *ec;
483         struct udev_enumerate *e;
484         struct udev_list_entry *entry;
485         struct udev_device *device;
486         const char *path;
487         struct wl_event_loop *loop;
488
489         ec = malloc(sizeof *ec);
490         if (ec == NULL)
491                 return NULL;
492
493         memset(ec, 0, sizeof *ec);
494         ec->udev = udev_new();
495         if (ec->udev == NULL) {
496                 fprintf(stderr, "failed to initialize udev context\n");
497                 return NULL;
498         }
499
500         e = udev_enumerate_new(ec->udev);
501         udev_enumerate_add_match_subsystem(e, "drm");
502         udev_enumerate_add_match_property(e, "WAYLAND_SEAT", "1");
503         udev_enumerate_scan_devices(e);
504         device = NULL;
505         udev_list_entry_foreach(entry, udev_enumerate_get_list_entry(e)) {
506                 path = udev_list_entry_get_name(entry);
507                 device = udev_device_new_from_syspath(ec->udev, path);
508                 break;
509         }
510         udev_enumerate_unref(e);
511
512         if (device == NULL) {
513                 fprintf(stderr, "no drm device found\n");
514                 return NULL;
515         }
516
517         ec->base.wl_display = display;
518         if (init_egl(ec, device) < 0) {
519                 fprintf(stderr, "failed to initialize egl\n");
520                 return NULL;
521         }
522
523         ec->base.destroy = drm_destroy;
524         ec->base.create_buffer = wlsc_shm_buffer_create;
525         ec->base.focus = 1;
526
527         glGenFramebuffers(1, &ec->base.fbo);
528         glBindFramebuffer(GL_FRAMEBUFFER, ec->base.fbo);
529
530         /* Can't init base class until we have a current egl context */
531         if (wlsc_compositor_init(&ec->base, display) < 0)
532                 return NULL;
533
534         if (create_outputs(ec, connector) < 0) {
535                 fprintf(stderr, "failed to create output for %s\n", path);
536                 return NULL;
537         }
538
539         evdev_input_add_devices(&ec->base, ec->udev);
540
541         loop = wl_display_get_event_loop(ec->base.wl_display);
542         ec->drm_source =
543                 wl_event_loop_add_fd(loop, ec->drm.fd,
544                                      WL_EVENT_READABLE, on_drm_input, ec);
545         ec->tty = tty_create(&ec->base);
546
547         ec->udev_monitor = udev_monitor_new_from_netlink(ec->udev, "udev");
548         if (ec->udev_monitor == NULL) {
549                 fprintf(stderr, "failed to intialize udev monitor\n");
550                 return NULL;
551         }
552         udev_monitor_filter_add_match_subsystem_devtype(ec->udev_monitor,
553                                                         "drm", NULL);
554         ec->udev_drm_source =
555                 wl_event_loop_add_fd(loop, udev_monitor_get_fd(ec->udev_monitor),
556                                      WL_EVENT_READABLE, udev_drm_event, ec);
557
558         if (udev_monitor_enable_receiving(ec->udev_monitor) < 0) {
559                 fprintf(stderr, "failed to enable udev-monitor receiving\n");
560                 return NULL;
561         }
562
563         return &ec->base;
564 }