1 /*****************************************************************************
2  * instance.c: VDPAU instance management for VLC
3  *****************************************************************************
4  * Copyright (C) 2013 Rémi Denis-Courmont
5  *
6  * This program is free software; you can redistribute it and/or modify it
7  * under the terms of the GNU Lesser General Public License as published by
8  * the Free Software Foundation; either version 2.1 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14  * GNU Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public License
17  * along with this program; if not, write to the Free Software Foundation,
18  * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
19  *****************************************************************************/
20 
21 #ifdef HAVE_CONFIG_H
22 # include <config.h>
23 #endif
24 
25 #include <stdlib.h>
26 #include <stdint.h>
27 #include <assert.h>
28 #include <pthread.h>
29 #include <X11/Xlib.h>
30 #include <vlc_common.h>
31 #include "vlc_vdpau.h"
32 
33 #pragma GCC visibility push(default)
34 
35 typedef struct vdp_instance
36 {
37     Display *display;
38     vdp_t *vdp;
39     VdpDevice device;
40 
41     int num; /**< X11 screen number */
42     char *name; /**< X11 display name */
43 
44     uintptr_t refs; /**< Reference count */
45     struct vdp_instance *next;
46 } vdp_instance_t;
47 
vdp_instance_create(const char * name,int num,vdp_instance_t ** pp)48 static VdpStatus vdp_instance_create(const char *name, int num,
49                                      vdp_instance_t **pp)
50 {
51     size_t namelen = (name != NULL) ? (strlen(name) + 1) : 0;
52     vdp_instance_t *vi = malloc(sizeof (*vi) + namelen);
53 
54     if (unlikely(vi == NULL))
55         return VDP_STATUS_RESOURCES;
56 
57     vi->display = XOpenDisplay(name);
58     if (vi->display == NULL)
59     {
60         free(vi);
61         return VDP_STATUS_ERROR;
62     }
63 
64     vi->next = NULL;
65     if (name != NULL)
66     {
67         vi->name = (void *)(vi + 1);
68         memcpy(vi->name, name, namelen);
69     }
70     else
71         vi->name = NULL;
72     if (num >= 0)
73         vi->num = num;
74     else
75         vi->num = XDefaultScreen(vi->display);
76     vi->refs = 1;
77 
78     VdpStatus err = vdp_create_x11(vi->display, vi->num,
79                                    &vi->vdp, &vi->device);
80     if (err != VDP_STATUS_OK)
81     {
82         XCloseDisplay(vi->display);
83         free(vi);
84         return err;
85     }
86     *pp = vi;
87     return VDP_STATUS_OK;
88 }
89 
vdp_instance_destroy(vdp_instance_t * vi)90 static void vdp_instance_destroy(vdp_instance_t *vi)
91 {
92     vdp_device_destroy(vi->vdp, vi->device);
93     vdp_destroy_x11(vi->vdp);
94     XCloseDisplay(vi->display);
95     free(vi);
96 }
97 
98 /** Compares two string pointers that might be NULL */
strnullcmp(const char * a,const char * b)99 static int strnullcmp(const char *a, const char *b)
100 {
101     if (b == NULL)
102         return a != NULL;
103     if (a == NULL)
104         return -1;
105     return strcmp(a, b);
106 }
107 
vicmp(const char * name,int num,const vdp_instance_t * vi)108 static int vicmp(const char *name, int num, const vdp_instance_t *vi)
109 {
110     int val = strnullcmp(name, vi->name);
111     if (val)
112         return val;
113 
114     if (num < 0)
115         num = XDefaultScreen(vi->display);
116     return num - vi->num;
117 }
118 
119 static vdp_instance_t *list = NULL;
120 
vdp_instance_lookup(const char * name,int num)121 static vdp_instance_t *vdp_instance_lookup(const char *name, int num)
122 {
123     vdp_instance_t *vi = NULL;
124 
125     for (vi = list; vi != NULL; vi = vi->next)
126     {
127         int val = vicmp(name, num, vi);
128         if (val == 0)
129         {
130             assert(vi->refs < UINTPTR_MAX);
131             vi->refs++;
132             break;
133         }
134     }
135     return vi;
136 }
137 
138 static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
139 
140 /** Finds an existing VDPAU instance for the given X11 display and screen.
141  * If not found, (try to) create a new one.
142  * @param display_name X11 display string, NULL for default
143  * @param snum X11 screen number, strictly negative for default
144  **/
vdp_get_x11(const char * display_name,int snum,vdp_t ** restrict vdpp,VdpDevice * restrict devicep)145 VdpStatus vdp_get_x11(const char *display_name, int snum,
146                       vdp_t **restrict vdpp, VdpDevice *restrict devicep)
147 {
148     vdp_instance_t *vi, *vi2;
149     VdpStatus err = VDP_STATUS_RESOURCES;
150 
151     if (display_name == NULL)
152     {
153         display_name = getenv("DISPLAY");
154         if (display_name == NULL)
155             return VDP_STATUS_ERROR;
156     }
157 
158     pthread_mutex_lock(&lock);
159     vi = vdp_instance_lookup(display_name, snum);
160     pthread_mutex_unlock(&lock);
161     if (vi != NULL)
162         goto found;
163 
164     err = vdp_instance_create(display_name, snum, &vi);
165     if (err != VDP_STATUS_OK)
166         return err;
167 
168     pthread_mutex_lock(&lock);
169     vi2 = vdp_instance_lookup(display_name, snum);
170     if (unlikely(vi2 != NULL))
171     {   /* Another thread created the instance (race condition corner case) */
172         pthread_mutex_unlock(&lock);
173         vdp_instance_destroy(vi);
174         vi = vi2;
175     }
176     else
177     {
178         vi->next = list;
179         list = vi;
180         pthread_mutex_unlock(&lock);
181     }
182 found:
183     *vdpp = vi->vdp;
184     *devicep = vi->device;
185     return VDP_STATUS_OK;
186 }
187 
vdp_hold_x11(vdp_t * vdp,VdpDevice * restrict devp)188 vdp_t *vdp_hold_x11(vdp_t *vdp, VdpDevice *restrict devp)
189 {
190     vdp_instance_t *vi, **pp = &list;
191 
192     pthread_mutex_lock(&lock);
193     for (;;)
194     {
195         vi = *pp;
196         assert(vi != NULL);
197         if (vi->vdp == vdp)
198             break;
199         pp = &vi->next;
200     }
201 
202     assert(vi->refs < UINTPTR_MAX);
203     vi->refs++;
204     pthread_mutex_unlock(&lock);
205 
206     if (devp != NULL)
207         *devp = vi->device;
208    return vdp;
209 }
210 
vdp_release_x11(vdp_t * vdp)211 void vdp_release_x11(vdp_t *vdp)
212 {
213     vdp_instance_t *vi, **pp = &list;
214 
215     pthread_mutex_lock(&lock);
216     for (;;)
217     {
218         vi = *pp;
219         assert(vi != NULL);
220         if (vi->vdp == vdp)
221             break;
222         pp = &vi->next;
223     }
224 
225     assert(vi->refs > 0);
226     vi->refs--;
227     if (vi->refs > 0)
228         vi = NULL; /* Keep the instance for now */
229     else
230         *pp = vi->next; /* Unlink the instance */
231     pthread_mutex_unlock(&lock);
232 
233     if (vi != NULL)
234         vdp_instance_destroy(vi);
235 }
236