WIP: start adding dri2videosink
[gstreamer-omap:gst-plugins-bad.git] / sys / dri2 / gstdri2util.c
1 /*
2  * GStreamer
3  *
4  * Copyright (C) 2012 Texas Instruments
5  *
6  * Authors:
7  *  Rob Clark <rob.clark@linaro.org>
8  *
9  * This library is free software; you can redistribute it and/or
10  * modify it under the terms of the GNU Lesser General Public
11  * License as published by the Free Software Foundation
12  * version 2.1 of the License.
13  *
14  * This library is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17  * Lesser General Public License for more details.
18  *
19  * You should have received a copy of the GNU Lesser General Public
20  * License along with this library; if not, write to the Free Software
21  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
22  */
23
24 #ifdef HAVE_CONFIG_H
25 #include "config.h"
26 #endif
27
28 #include <ctype.h>
29
30 #include <gst/video/video-crop.h>
31
32 #include "gstdri2util.h"
33 #include "gstdri2bufferpool.h"
34
35 static Bool WireToEvent (Display * dpy, XExtDisplayInfo * info,
36     XEvent * event, xEvent * wire)
37 {
38   switch ((wire->u.u.type & 0x7f) - info->codes->first_event) {
39
40   case DRI2_BufferSwapComplete: {
41     //    xDRI2BufferSwapComplete *awire = (xDRI2BufferSwapComplete *)wire;
42     // TODO use this to know when the previous buffer is no longer visible..
43     GST_LOG ("BufferSwapComplete");
44     return True;
45   }
46   case DRI2_InvalidateBuffers: {
47     //    xDRI2InvalidateBuffers *awire = (xDRI2InvalidateBuffers *)wire;
48     GST_LOG ("InvalidateBuffers");
49     //    dri2InvalidateBuffers(dpy, awire->drawable);
50     return False;
51   }
52   default:
53     /* client doesn't support server event */
54     break;
55   }
56
57   return False;
58 }
59
60 static Status EventToWire (Display * dpy, XExtDisplayInfo * info,
61     XEvent * event, xEvent * wire)
62 {
63   switch (event->type) {
64   default:
65     /* client doesn't support server event */
66     break;
67   }
68
69   return Success;
70 }
71
72 static const DRI2EventOps ops = {
73     .WireToEvent = WireToEvent,
74     .EventToWire = EventToWire,
75 };
76
77 static DRI2Buffer * get_buffer (GstDRI2Window * xwindow, guint attach,
78     gint width, gint height, guint32 format);
79
80 static Bool is_fourcc(unsigned int val)
81 {
82   char *str = (char *)&val;
83   return isalnum(str[0]) && isalnum(str[1]) &&
84       isalnum(str[2]) && isalnum(str[3]);
85 }
86
87 /*
88  * GstDRI2DrawContext
89  */
90
91 /* This function calculates the pixel aspect ratio based on the properties
92  * in the xcontext structure and stores it there.
93  */
94 static void
95 gst_dri2context_calculate_pixel_aspect_ratio (GstDRI2Context * dcontext)
96 {
97   static const gint par[][2] = {
98     {1, 1},                     /* regular screen */
99     {16, 15},                   /* PAL TV */
100     {11, 10},                   /* 525 line Rec.601 video */
101     {54, 59},                   /* 625 line Rec.601 video */
102     {64, 45},                   /* 1280x1024 on 16:9 display */
103     {5, 3},                     /* 1280x1024 on 4:3 display */
104     {4, 3}                      /* 800x600 on 16:9 display */
105   };
106   gint i;
107   gint index;
108   gdouble ratio;
109   gdouble delta;
110
111 #define DELTA(idx) (ABS (ratio - ((gdouble) par[idx][0] / par[idx][1])))
112
113   /* first calculate the "real" ratio; which is the "physical" w/h divided
114    * by the w/h in pixels of the display
115    *
116    * TODO:
117   ratio = (gdouble) (dcontext->physical_width * dcontext->display_height)
118       / (dcontext->physical_height * dcontext->display_width);
119    */
120
121   /* XXX */
122   ratio = 1;
123
124   GST_DEBUG ("calculated pixel aspect ratio: %f", ratio);
125   /* now find the one from par[][2] with the lowest delta to the real one */
126   delta = DELTA (0);
127   index = 0;
128
129   for (i = 1; i < sizeof (par) / (sizeof (gint) * 2); ++i) {
130     gdouble this_delta = DELTA (i);
131
132     if (this_delta < delta) {
133       index = i;
134       delta = this_delta;
135     }
136   }
137
138   GST_DEBUG ("Decided on index %d (%d/%d)", index,
139       par[index][0], par[index][1]);
140
141   g_free (dcontext->par);
142   dcontext->par = g_new0 (GValue, 1);
143   g_value_init (dcontext->par, GST_TYPE_FRACTION);
144   gst_value_set_fraction (dcontext->par, par[index][0], par[index][1]);
145   GST_DEBUG ("set dcontext PAR to %d/%d",
146       gst_value_get_fraction_numerator (dcontext->par),
147       gst_value_get_fraction_denominator (dcontext->par));
148 }
149
150 GstDRI2Context *
151 gst_dri2context_new (GstElement * elem)
152 {
153   GstDRI2Context *dcontext;
154   Window root;
155   drm_magic_t magic;
156   int eventBase, errorBase, major, minor;
157   char *driver, *device;
158   unsigned int i, nformats, *formats;
159   int fd;
160
161   dcontext = g_new0 (GstDRI2Context, 1);
162   dcontext->elem = elem;
163   dcontext->x_lock = g_mutex_new ();
164
165   dcontext->x_display = XOpenDisplay (NULL);
166   if (!DRI2InitDisplay(dcontext->x_display, &ops)) {
167     GST_ERROR_OBJECT (elem, "DRI2InitDisplay failed");
168     goto fail;
169   }
170
171   if (!DRI2QueryExtension (dcontext->x_display, &eventBase, &errorBase)) {
172     GST_ERROR_OBJECT (elem, "DRI2QueryExtension failed");
173     goto fail;
174   }
175
176   GST_DEBUG_OBJECT (elem, "DRI2QueryExtension: "
177       "eventBase=%d, errorBase=%d", eventBase, errorBase);
178
179   if (!DRI2QueryVersion (dcontext->x_display, &major, &minor)) {
180     GST_ERROR_OBJECT (elem, "DRI2QueryVersion failed");
181     goto fail;
182   }
183
184   GST_DEBUG_OBJECT (elem, "DRI2QueryVersion: major=%d, minor=%d",
185       major, minor);
186
187   root = RootWindow (dcontext->x_display,
188       DefaultScreen (dcontext->x_display));
189
190   if (!DRI2Connect (dcontext->x_display, root,
191       DRI2DriverXV, &driver, &device)) {
192     GST_ERROR_OBJECT (elem, "DRI2Connect failed");
193     goto fail;
194   }
195
196   GST_DEBUG_OBJECT (elem, "DRI2Connect: driver=%s, device=%s",
197       driver, device);
198
199   fd = open (device, O_RDWR);
200   if (fd < 0) {
201     GST_ERROR_OBJECT (elem, "open failed");
202     goto fail;
203   }
204
205   if (drmGetMagic (fd, &magic)) {
206     GST_ERROR_OBJECT (elem, "drmGetMagic failed");
207     goto fail;
208   }
209
210   if (!DRI2Authenticate (dcontext->x_display, root, magic)) {
211     GST_ERROR_OBJECT (elem, "DRI2Authenticate failed");
212     goto fail;
213   }
214
215   dcontext->drm_fd = fd;
216   dcontext->dev = omap_device_new (fd);
217
218   if (!DRI2GetFormats (dcontext->x_display, root, &nformats, &formats)) {
219     GST_ERROR_OBJECT (elem, "DRI2GetFormats failed");
220     goto fail;
221   }
222
223   if (nformats == 0) {
224     GST_ERROR_OBJECT (elem, "no formats!");
225     goto fail;
226   }
227
228   /* print out supported formats */
229   GST_DEBUG_OBJECT (elem, "Found %d supported formats:", nformats);
230   for (i = 0; i < nformats; i++) {
231     if (is_fourcc(formats[i])) {
232       GST_DEBUG_OBJECT (elem, "  %d: %08x (\"%.4s\")", i, formats[i],
233           (char *)&formats[i]);
234     } else {
235       GST_DEBUG_OBJECT (elem, "  %d: %08x (device dependent)", i, formats[i]);
236     }
237   }
238
239   free(formats);
240
241   gst_dri2context_calculate_pixel_aspect_ratio (dcontext);
242
243   dcontext->black = XBlackPixel (dcontext->x_display, dcontext->screen_num);
244
245   return dcontext;
246
247 fail:
248   /* XXX: release resources */
249   return NULL;
250 }
251
252 void
253 gst_dri2context_delete (GstDRI2Context *dcontext)
254 {
255   g_free (dcontext->par);
256
257   g_mutex_lock (dcontext->x_lock);
258   XCloseDisplay (dcontext->x_display);
259   g_mutex_unlock (dcontext->x_lock);
260   g_mutex_free (dcontext->x_lock);
261
262   omap_device_del (dcontext->dev);
263   drmClose (dcontext->drm_fd);
264
265   g_free (dcontext);
266 }
267
268 /*
269  * GstDRI2Window
270  */
271
272 /* NOTES:
273  * at startup (or on first buffer allocation?) request front buffer..
274  * otherwise I think we can do GetBuffers 1 at a time, w/ different
275  * attachment points.. use width==0, height==0 to destroy the buffer
276  * Keep the table of attachment->buffer globally, to handle resolution
277  * changes.. the old bufferpool is torn down, but still goes via the
278  * per video-sink table of attachments, because during the transition
279  * period we could have some not-yet-displayed buffers at the previous
280  * dimensions/format..
281  */
282
283 GstDRI2Window *
284 gst_dri2window_new_from_handle (GstDRI2Context *dcontext, XID xwindow_id)
285 {
286   GstDRI2Window *xwindow;
287   XWindowAttributes attr;
288
289   xwindow = g_new0 (GstDRI2Window, 1);
290   xwindow->dcontext = dcontext;
291   xwindow->window = xwindow_id;
292   xwindow->pool_lock = g_mutex_new ();
293   xwindow->buffer_pool = NULL;
294   xwindow->pool_valid = FALSE;
295
296   /* Set the event we want to receive and create a GC */
297   g_mutex_lock (dcontext->x_lock);
298
299   XGetWindowAttributes (dcontext->x_display, xwindow->window,
300       &attr);
301
302   xwindow->width = attr.width;
303   xwindow->height = attr.height;
304
305   /* We have to do that to prevent X from redrawing the background on
306    * ConfigureNotify. This takes away flickering of video when resizing. */
307   XSetWindowBackgroundPixmap (dcontext->x_display,
308       xwindow->window, None);
309
310   XMapWindow (dcontext->x_display, xwindow->window);
311
312   xwindow->gc = XCreateGC (dcontext->x_display,
313       xwindow->window, 0, NULL);
314   g_mutex_unlock (dcontext->x_lock);
315
316   DRI2CreateDrawable (dcontext->x_display, xwindow->window);
317
318   /* request the front buffer.. we don't need to keep it, just to
319    * request it.. otherwise DRI2 core on xserver side gets miffed:
320    *   [DRI2] swap_buffers: drawable has no back or front?
321    */
322   free (get_buffer (xwindow, DRI2BufferFrontLeft,
323       xwindow->width, xwindow->height, 32));
324
325   return xwindow;
326 }
327
328 GstDRI2Window *
329 gst_dri2window_new (GstDRI2Context * dcontext, gint width, gint height)
330 {
331   GstDRI2Window *xwindow;
332   Window root;
333   Atom wm_delete;
334   XID xwindow_id;
335
336   g_mutex_lock (dcontext->x_lock);
337
338   GST_DEBUG_OBJECT (dcontext->elem, "creating window: %dx%d", width, height);
339
340   root = DefaultRootWindow (dcontext->x_display);
341   xwindow_id = XCreateSimpleWindow (dcontext->x_display, root, 0, 0,
342       width, height, 2, 2, dcontext->black);
343
344
345   /* Tell the window manager we'd like delete client messages instead of
346    * being killed */
347   wm_delete = XInternAtom (dcontext->x_display, "WM_DELETE_WINDOW", True);
348   if (wm_delete != None) {
349     (void) XSetWMProtocols (dcontext->x_display, xwindow_id,
350         &wm_delete, 1);
351   }
352
353   g_mutex_unlock (dcontext->x_lock);
354
355   xwindow = gst_dri2window_new_from_handle (dcontext, xwindow_id);
356   xwindow->internal = TRUE;
357
358   return xwindow;
359 }
360
361 void
362 gst_dri2window_delete (GstDRI2Window * xwindow)
363 {
364   GstDRI2Context *dcontext = xwindow->dcontext;
365
366   g_mutex_lock (xwindow->pool_lock);
367   xwindow->pool_valid = FALSE;
368   if (xwindow->buffer_pool) {
369     gst_drm_buffer_pool_destroy (xwindow->buffer_pool);
370     xwindow->buffer_pool = NULL;
371   }
372   g_mutex_unlock (xwindow->pool_lock);
373
374   g_mutex_free (xwindow->pool_lock);
375
376   g_mutex_lock (dcontext->x_lock);
377
378   DRI2DestroyDrawable (dcontext->x_display, xwindow->window);
379
380   /* If we did not create the window we just free the GC and let it live */
381   if (xwindow->internal)
382     XDestroyWindow (dcontext->x_display, xwindow->window);
383   else
384     XSelectInput (dcontext->x_display, xwindow->window, 0);
385
386   XFreeGC (dcontext->x_display, xwindow->gc);
387
388   XSync (dcontext->x_display, FALSE);
389
390   // XXX free xwindow->dri2bufs
391   // TODO we probably want xwindow to be a refcnt'd miniobj so we don't end w/
392   // dri2buffer's referencing deleted xwindow's..
393
394   g_mutex_unlock (dcontext->x_lock);
395
396   g_free (xwindow);
397 }
398
399 /* call with x_lock held */
400 void
401 gst_dri2window_update_geometry (GstDRI2Window * xwindow)
402 {
403   XWindowAttributes attr;
404
405   XGetWindowAttributes (xwindow->dcontext->x_display,
406       xwindow->window, &attr);
407
408   xwindow->width  = attr.width;
409   xwindow->height = attr.height;
410 }
411
412 void
413 gst_dri2window_set_pool_valid (GstDRI2Window * xwindow, gboolean valid)
414 {
415   g_mutex_lock (xwindow->pool_lock);
416   xwindow->pool_valid = valid;
417   g_mutex_unlock (xwindow->pool_lock);
418 }
419
420 void
421 gst_dri2window_check_caps (GstDRI2Window * xwindow, GstCaps * caps)
422 {
423   g_mutex_lock (xwindow->pool_lock);
424   if (xwindow->buffer_pool) {
425     if (gst_drm_buffer_pool_check_caps (xwindow->buffer_pool, caps)) {
426       GST_INFO_OBJECT (xwindow->dcontext->elem, "caps change");
427       gst_drm_buffer_pool_destroy (xwindow->buffer_pool);
428       xwindow->buffer_pool = NULL;
429     }
430   }
431   g_mutex_unlock (xwindow->pool_lock);
432 }
433
434 static inline gboolean
435 ok_buffer (GstDRI2Window * xwindow, GstBuffer * buf)
436 {
437   return GST_IS_DRI2_BUFFER (buf) &&
438       (GST_DRI2_BUFFER_POOL (GST_DRM_BUFFER (buf)->pool)->xwindow == xwindow);
439
440 }
441
442 GstFlowReturn
443 gst_dri2window_buffer_show (GstDRI2Window * xwindow, GstBuffer * buf)
444 {
445   GstDRI2Context *dcontext = xwindow->dcontext;
446   GstDRI2Buffer *dri2buf;
447   GstVideoCrop *crop;
448   CARD64 count;
449   BoxRec b;
450
451   if (! ok_buffer (xwindow, buf)) {
452     GST_WARNING_OBJECT (dcontext->elem, "unexpected buffer: %p", buf);
453     return GST_FLOW_UNEXPECTED;
454   }
455
456   dri2buf = GST_DRI2_BUFFER (buf);
457
458   crop = gst_buffer_get_video_crop (buf);
459   if (crop) {
460     b.x1 = gst_video_crop_left (crop);
461     b.y1 = gst_video_crop_top (crop);
462     b.x2 = b.x1 + gst_video_crop_width (crop) - 1;
463     b.y2 = b.y1 + gst_video_crop_height (crop) - 1;
464   } else {
465     b.x1 = 0;
466     b.y1 = 0;
467     b.x2 = GST_DRM_BUFFER (dri2buf)->pool->width - 1;
468     b.y2 = GST_DRM_BUFFER (dri2buf)->pool->height - 1;
469   }
470
471   g_mutex_lock (dcontext->x_lock);
472   DRI2SwapBuffersVid (dcontext->x_display, xwindow->window, 0, 0, 0,
473       &count, dri2buf->dri2buf->attachment, &b);
474   /* TODO: probably should wait for DRI2_BufferSwapComplete instead..
475    * although that probably depends on someone making an x11 call to
476    * dispatch the events
477    */
478   DRI2WaitSBC (dcontext->x_display, xwindow->window, count,
479       /* just re-use count as a valid ptr.. we don't need ust/msc/sbc: */
480       &count, &count, &count);
481   g_mutex_unlock (dcontext->x_lock);
482
483   return GST_FLOW_OK;
484 }
485
486 GstBuffer *
487 gst_dri2window_buffer_prepare (GstDRI2Window * xwindow, GstBuffer * buf)
488 {
489   GstBuffer *newbuf = NULL;
490
491   if (! ok_buffer (xwindow, buf)) {
492
493     gst_dri2window_buffer_alloc (xwindow, GST_BUFFER_SIZE (buf),
494         GST_BUFFER_CAPS (buf), &newbuf);
495
496     if (newbuf) {
497       GST_DEBUG_OBJECT (xwindow->dcontext->elem,
498           "slow-path.. I got a %s so I need to memcpy",
499           g_type_name (G_OBJECT_TYPE (buf)));
500 #if 0 // XXX
501       memcpy (GST_BUFFER_DATA (newbuf),
502           GST_BUFFER_DATA (buf),
503           MIN (GST_BUFFER_SIZE (newbuf), GST_BUFFER_SIZE (buf)));
504 #else
505       GST_DEBUG_OBJECT (xwindow->dcontext->elem,
506           "stubbed: memcpy(%p, %p, %d)",
507           GST_BUFFER_DATA (newbuf),
508           GST_BUFFER_DATA (buf),
509           MIN (GST_BUFFER_SIZE (newbuf), GST_BUFFER_SIZE (buf)));
510 #endif
511     }
512   }
513
514   return newbuf;
515 }
516
517 GstFlowReturn
518 gst_dri2window_buffer_alloc (GstDRI2Window * xwindow, guint size,
519     GstCaps * caps, GstBuffer ** buf)
520 {
521   GstDRI2Context *dcontext = xwindow->dcontext;
522   GstFlowReturn ret = GST_FLOW_ERROR;
523
524   *buf = NULL;
525
526   g_mutex_lock (xwindow->pool_lock);
527 #if 0
528   /* double check if we need this.. if we do, we probably need to
529    * move pool_valid back to dri2videosink itself, because the
530    * window can be created after the PAUSED->READY state transition
531    */
532   if (G_UNLIKELY (! xwindow->pool_valid)) {
533     GST_DEBUG_OBJECT (dcontext->elem, "the pool is flushing");
534     ret = GST_FLOW_WRONG_STATE;
535     g_mutex_unlock (xwindow->pool_lock);
536     goto beach;
537   }
538 #endif
539
540   /* initialize the buffer pool if not initialized yet */
541   if (G_UNLIKELY (!xwindow->buffer_pool ||
542       gst_drm_buffer_pool_size (xwindow->buffer_pool) != size)) {
543
544     if (xwindow->buffer_pool) {
545       GST_INFO_OBJECT (dcontext->elem, "size change");
546       gst_drm_buffer_pool_destroy (xwindow->buffer_pool);
547     }
548
549     GST_LOG_OBJECT (dcontext->elem, "Creating buffer pool");
550     xwindow->buffer_pool = GST_DRM_BUFFER_POOL (gst_dri2_buffer_pool_new (
551         xwindow, dcontext->drm_fd, caps, size));
552     if (!xwindow->buffer_pool) {
553       goto beach;
554     }
555   }
556
557   *buf = GST_BUFFER (gst_drm_buffer_pool_get (xwindow->buffer_pool, FALSE));
558
559   if (*buf)
560     ret = GST_FLOW_OK;
561
562 beach:
563   g_mutex_unlock (xwindow->pool_lock);
564   return ret;
565 }
566
567 /*
568  * These are used by the bufferpool to allocate buffers.. the bufferpool
569  * needs to go thru the GstDRI2Window, because we need one place to track
570  * which attachment points are in use and which are not to hande cases of
571  * switching between resolutions, where the bufferpool is replaced but
572  * with a transition period of having both buffers of the old and new size
573  * floating around
574  */
575
576 static DRI2Buffer *
577 get_buffer (GstDRI2Window * xwindow, guint attach, gint width, gint height,
578     guint32 format)
579 {
580   GstDRI2Context *dcontext = xwindow->dcontext;
581   int nbufs = 1;
582   unsigned attachments[] = { attach, format };
583   DRI2Buffer *dri2buf;
584   g_mutex_lock (dcontext->x_lock);
585   dri2buf = DRI2GetBuffersVid(dcontext->x_display, xwindow->window,
586       width, height, attachments, nbufs, &nbufs);
587   g_mutex_unlock (dcontext->x_lock);
588   GST_DEBUG_OBJECT (dcontext->elem, "got %d buffer(s)", nbufs);
589   if (nbufs != 1) {
590     free (dri2buf);
591     return NULL;
592   }
593   return dri2buf;
594 }
595
596 DRI2Buffer *
597 gst_dri2window_get_dri2buffer (GstDRI2Window * xwindow, gint width, gint height,
598     guint32 format)
599 {
600   GstDRI2Context *dcontext = xwindow->dcontext;
601   int idx;
602
603   /* find an empty slot, note first slot is the (fake) front buffer,
604    * attached when the GstDRI2Window is constructed:
605    */
606   for (idx = 0; idx < G_N_ELEMENTS (xwindow->dri2bufs); idx++) {
607     if (!xwindow->dri2bufs[idx]) {
608       xwindow->dri2bufs[idx] = get_buffer (xwindow, idx + 1,
609           width, height, format);
610       g_warn_if_fail ((xwindow->dri2bufs[idx]->attachment - 1) == idx);
611       return xwindow->dri2bufs[idx];
612     }
613   }
614
615   GST_ERROR_OBJECT (dcontext->elem, "out of buffer slots");
616
617   return NULL;
618 }
619
620 void
621 gst_dri2window_free_dri2buffer (GstDRI2Window * xwindow, DRI2Buffer * dri2buf)
622 {
623   int idx = dri2buf->attachment - 1;
624   get_buffer (xwindow, dri2buf->attachment, 0, 0, 0);
625   free (xwindow->dri2bufs[idx]);
626   xwindow->dri2bufs[idx] = NULL;
627 }