vdpau_wrapper.c revision a4f78def
1/*
2 * Copyright (c) 2008-2015 NVIDIA Corporation
3 *
4 * Permission is hereby granted, free of charge, to any person obtaining a copy
5 * of this software and associated documentation files (the "Software"), to deal
6 * in the Software without restriction, including without limitation the rights
7 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 * copies of the Software, and to permit persons to whom the Software is
9 * furnished to do so, subject to the following conditions:
10 *
11 * The above copyright notice and this permission notice (including the next
12 * paragraph) shall be included in all copies or substantial portions of the
13 * Software.
14 *
15 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
18 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 * SOFTWARE.
22 */
23
24#ifdef HAVE_CONFIG_H
25#include "config.h"
26#endif
27
28#include <dlfcn.h>
29#include <limits.h>
30#include <pthread.h>
31#include <stdio.h>
32#include <stdlib.h>
33#include <string.h>
34#include <unistd.h>
35#include <vdpau/vdpau_x11.h>
36#if DRI2
37#include "mesa_dri2.h"
38#include <X11/Xlib.h>
39#endif
40#include "util.h"
41
42typedef void SetDllHandle(
43    void * driver_dll_handle
44);
45
46static void * _vdp_backend_dll;
47static void * _vdp_trace_dll;
48static void * _vdp_driver_dll;
49static VdpDeviceCreateX11 * _vdp_imp_device_create_x11_proc;
50
51#if defined(__GNUC__)
52
53static void _vdp_close_driver(void) __attribute__((destructor));
54
55#endif
56
57#if DEBUG
58
59static void _vdp_wrapper_error_breakpoint(char const * file, int line, char const * function)
60{
61    fprintf(stderr, "VDPAU wrapper: Error detected at %s:%d %s()\n", file, line, function);
62}
63
64#define _VDP_ERROR_BREAKPOINT() _vdp_wrapper_error_breakpoint(__FILE__, __LINE__, __FUNCTION__)
65
66#else
67
68#define _VDP_ERROR_BREAKPOINT()
69
70#endif
71
72#define DRIVER_FALLBACK_LIB_FORMAT "libvdpau_%s.so"
73#define DRIVER_LIB_FORMAT "%s/libvdpau_%s.so.1"
74
75static char * _vdp_get_driver_name_from_dri2(
76    Display *             display,
77    int                   screen
78)
79{
80    char * driver_name = NULL;
81#if DRI2
82    Window root = RootWindow(display, screen);
83    int event_base, error_base;
84    int major, minor;
85    char * device_name;
86
87    if (!_vdp_DRI2QueryExtension(display, &event_base, &error_base)) {
88        return NULL;
89    }
90
91    if (!_vdp_DRI2QueryVersion(display, &major, &minor) ||
92            (major < 1 || (major == 1 && minor < 2))) {
93        _vdp_DRI2RemoveExtension(display);
94        return NULL;
95    }
96
97    if (!_vdp_DRI2Connect(display, root, &driver_name, &device_name)) {
98        _vdp_DRI2RemoveExtension(display);
99        return NULL;
100    }
101
102    XFree(device_name);
103    _vdp_DRI2RemoveExtension(display);
104#else
105    (void) display; (void) screen;
106#endif /* DRI2 */
107    return driver_name;
108}
109
110static VdpStatus _vdp_open_driver(
111    Display *             display,
112    int                   screen)
113{
114    char const * vdpau_driver;
115    char * vdpau_driver_dri2 = NULL;
116    const char * vdpau_driver_path = NULL;
117    char         vdpau_driver_lib[PATH_MAX];
118    char const * vdpau_trace;
119    char const * func_name;
120
121    vdpau_driver = secure_getenv("VDPAU_DRIVER");
122    if (vdpau_driver) {
123        if (strchr(vdpau_driver, '/')) {
124            vdpau_driver = NULL;
125        }
126    }
127    if (!vdpau_driver) {
128        vdpau_driver = vdpau_driver_dri2 =
129            _vdp_get_driver_name_from_dri2(display, screen);
130    }
131    if (!vdpau_driver) {
132        vdpau_driver = "nvidia";
133    }
134
135    /* Don't allow setuid apps to use VDPAU_DRIVER_PATH */
136    vdpau_driver_path = secure_getenv("VDPAU_DRIVER_PATH");
137    if (vdpau_driver_path &&
138        snprintf(vdpau_driver_lib, sizeof(vdpau_driver_lib),
139                 DRIVER_LIB_FORMAT, vdpau_driver_path, vdpau_driver) <
140            sizeof(vdpau_driver_lib)) {
141        _vdp_driver_dll = dlopen(vdpau_driver_lib, RTLD_NOW | RTLD_GLOBAL);
142    }
143
144    /* Fallback to VDPAU_MODULEDIR when VDPAU_DRIVER_PATH is not set,
145     * or if we fail to create the driver path/dlopen the library. */
146    if (!_vdp_driver_dll) {
147        if (snprintf(vdpau_driver_lib, sizeof(vdpau_driver_lib),
148                     DRIVER_LIB_FORMAT, VDPAU_MODULEDIR, vdpau_driver) >=
149                sizeof(vdpau_driver_lib)) {
150            fprintf(stderr, "Failed to construct driver path: path too long\n");
151            if (vdpau_driver_dri2) {
152                XFree(vdpau_driver_dri2);
153                vdpau_driver_dri2 = NULL;
154            }
155            _VDP_ERROR_BREAKPOINT();
156            return VDP_STATUS_NO_IMPLEMENTATION;
157        }
158        else {
159            _vdp_driver_dll = dlopen(vdpau_driver_lib, RTLD_NOW | RTLD_GLOBAL);
160        }
161    }
162
163    if (!_vdp_driver_dll) {
164        /* Try again using the old path, which is guaranteed to fit in PATH_MAX
165         * if the complete path fit above. */
166        snprintf(vdpau_driver_lib, sizeof(vdpau_driver_lib),
167                 DRIVER_FALLBACK_LIB_FORMAT, vdpau_driver);
168        _vdp_driver_dll = dlopen(vdpau_driver_lib, RTLD_NOW | RTLD_GLOBAL);
169    }
170
171    if (vdpau_driver_dri2) {
172        XFree(vdpau_driver_dri2);
173        vdpau_driver_dri2 = NULL;
174    }
175
176    if (!_vdp_driver_dll) {
177        fprintf(stderr, "Failed to open VDPAU backend %s\n", dlerror());
178        _VDP_ERROR_BREAKPOINT();
179        return VDP_STATUS_NO_IMPLEMENTATION;
180    }
181
182    _vdp_backend_dll = _vdp_driver_dll;
183
184    vdpau_trace = secure_getenv("VDPAU_TRACE");
185    if (vdpau_trace && atoi(vdpau_trace)) {
186        SetDllHandle * set_dll_handle;
187
188        _vdp_trace_dll = dlopen(VDPAU_MODULEDIR "/libvdpau_trace.so.1",
189                                RTLD_NOW | RTLD_GLOBAL);
190        if (!_vdp_trace_dll) {
191            fprintf(stderr, "Failed to open VDPAU trace library %s\n", dlerror());
192            _VDP_ERROR_BREAKPOINT();
193            return VDP_STATUS_NO_IMPLEMENTATION;
194        }
195
196        set_dll_handle = (SetDllHandle*)dlsym(
197            _vdp_trace_dll,
198            "vdp_trace_set_backend_handle"
199        );
200        if (!set_dll_handle) {
201            fprintf(stderr, "%s\n", dlerror());
202            _VDP_ERROR_BREAKPOINT();
203            return VDP_STATUS_NO_IMPLEMENTATION;
204        }
205
206        set_dll_handle(_vdp_backend_dll);
207
208        _vdp_backend_dll = _vdp_trace_dll;
209
210        func_name = "vdp_trace_device_create_x11";
211    }
212    else {
213        func_name = "vdp_imp_device_create_x11";
214    }
215
216    _vdp_imp_device_create_x11_proc = (VdpDeviceCreateX11*)dlsym(
217        _vdp_backend_dll,
218        func_name
219    );
220    if (!_vdp_imp_device_create_x11_proc) {
221        fprintf(stderr, "%s\n", dlerror());
222        _VDP_ERROR_BREAKPOINT();
223        return VDP_STATUS_NO_IMPLEMENTATION;
224    }
225
226    return VDP_STATUS_OK;
227}
228
229static void _vdp_close_driver(void)
230{
231    if (_vdp_driver_dll) {
232        dlclose(_vdp_driver_dll);
233        _vdp_driver_dll = NULL;
234    }
235    if (_vdp_trace_dll) {
236        dlclose(_vdp_trace_dll);
237        _vdp_trace_dll = NULL;
238    }
239    _vdp_backend_dll = NULL;
240    _vdp_imp_device_create_x11_proc = NULL;
241}
242
243static VdpGetProcAddress * _imp_get_proc_address;
244static VdpVideoSurfacePutBitsYCbCr * _imp_vid_put_bits_y_cb_cr;
245static VdpPresentationQueueSetBackgroundColor * _imp_pq_set_bg_color;
246static int _running_under_flash;
247static int _enable_flash_uv_swap = 1;
248static int _disable_flash_pq_bg_color = 1;
249
250static VdpStatus vid_put_bits_y_cb_cr_swapped(
251    VdpVideoSurface      surface,
252    VdpYCbCrFormat       source_ycbcr_format,
253    void const * const * source_data,
254    uint32_t const *     source_pitches
255)
256{
257    void const * data_reordered[3];
258    void const * const * data;
259
260    if (source_ycbcr_format == VDP_YCBCR_FORMAT_YV12) {
261        data_reordered[0] = source_data[0];
262        data_reordered[1] = source_data[2];
263        data_reordered[2] = source_data[1];
264        /*
265         * source_pitches[1] and source_pitches[2] should be equal,
266         * so no need to re-order.
267         */
268        data = data_reordered;
269    }
270    else {
271        data = source_data;
272    }
273
274    return _imp_vid_put_bits_y_cb_cr(
275        surface,
276        source_ycbcr_format,
277        data,
278        source_pitches
279    );
280}
281
282static VdpStatus pq_set_bg_color_noop(
283    VdpPresentationQueue presentation_queue,
284    VdpColor * const     background_color
285)
286{
287    (void) presentation_queue; (void) background_color;
288    return VDP_STATUS_OK;
289}
290
291static VdpStatus vdp_wrapper_get_proc_address(
292    VdpDevice device,
293    VdpFuncId function_id,
294    /* output parameters follow */
295    void * *  function_pointer
296)
297{
298    VdpStatus status;
299
300    status = _imp_get_proc_address(device, function_id, function_pointer);
301    if (status != VDP_STATUS_OK) {
302        return status;
303    }
304
305    if (_running_under_flash) {
306        switch (function_id) {
307        case VDP_FUNC_ID_VIDEO_SURFACE_PUT_BITS_Y_CB_CR:
308            if (_enable_flash_uv_swap) {
309                _imp_vid_put_bits_y_cb_cr = *function_pointer;
310                *function_pointer = vid_put_bits_y_cb_cr_swapped;
311            }
312            break;
313        case VDP_FUNC_ID_PRESENTATION_QUEUE_SET_BACKGROUND_COLOR:
314            if (_disable_flash_pq_bg_color) {
315                _imp_pq_set_bg_color = *function_pointer;
316                *function_pointer = pq_set_bg_color_noop;
317            }
318            break;
319        default:
320            break;
321        }
322    }
323
324    return VDP_STATUS_OK;
325}
326
327static void init_running_under_flash(void)
328{
329    FILE *fp;
330    char buffer[1024];
331    int ret, i;
332
333    fp = fopen("/proc/self/cmdline", "r");
334    if (!fp) {
335        return;
336    }
337    ret = fread(buffer, 1, sizeof(buffer) - 1, fp);
338    fclose(fp);
339    if (ret < 0) {
340        return;
341    }
342    /*
343     * Sometimes the file contains null between arguments. Wipe these out so
344     * strstr doesn't stop early.
345     */
346    for (i = 0; i < ret; i++) {
347        if (buffer[i] == '\0') {
348            buffer[i] = 'x';
349        }
350    }
351    buffer[ret] = '\0';
352
353    if (strstr(buffer, "libflashplayer") != NULL) {
354        _running_under_flash = 1;
355    }
356}
357
358static void init_config(void)
359{
360    FILE *fp;
361    char buffer[1024];
362
363    fp = fopen(VDPAU_SYSCONFDIR "/vdpau_wrapper.cfg", "r");
364    if (!fp) {
365        return;
366    }
367
368    while (fgets(buffer, sizeof(buffer), fp) != NULL) {
369        char * equals = strchr(buffer, '=');
370        char * param;
371
372        if (equals == NULL) {
373            continue;
374        }
375
376        *equals = '\0';
377        param = equals + 1;
378
379        if (!strcmp(buffer, "enable_flash_uv_swap")) {
380            _enable_flash_uv_swap = atoi(param);
381        }
382        else if (!strcmp(buffer, "disable_flash_pq_bg_color")) {
383            _disable_flash_pq_bg_color = atoi(param);
384        }
385    }
386
387    fclose(fp);
388}
389
390static void init_fixes(void)
391{
392    init_running_under_flash();
393    init_config();
394}
395
396VdpStatus vdp_device_create_x11(
397    Display *             display,
398    int                   screen,
399    /* output parameters follow */
400    VdpDevice *           device,
401    VdpGetProcAddress * * get_proc_address
402)
403{
404    static pthread_once_t once = PTHREAD_ONCE_INIT;
405    static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
406    VdpGetProcAddress *gpa;
407    VdpStatus status = VDP_STATUS_OK;
408
409    pthread_once(&once, init_fixes);
410
411    pthread_mutex_lock(&lock);
412    if (!_vdp_imp_device_create_x11_proc) {
413        status = _vdp_open_driver(display, screen);
414        if (status != VDP_STATUS_OK)
415            _vdp_close_driver();
416    }
417    pthread_mutex_unlock(&lock);
418
419    if (status != VDP_STATUS_OK)
420        return status;
421
422    status = _vdp_imp_device_create_x11_proc(display, screen, device, &gpa);
423    if (status != VDP_STATUS_OK) {
424        return status;
425    }
426
427    *get_proc_address = vdp_wrapper_get_proc_address;
428
429    pthread_mutex_lock(&lock);
430    if (_imp_get_proc_address != gpa) {
431        if (_imp_get_proc_address == NULL)
432            _imp_get_proc_address = gpa;
433        else
434        /* Currently the wrapper can only deal with one back-end.
435         * This should never happen, but better safe than sorry. */
436            status = VDP_STATUS_NO_IMPLEMENTATION;
437    }
438    pthread_mutex_unlock(&lock);
439
440    if (status != VDP_STATUS_OK) {
441        void *pv;
442
443        if (gpa(*device, VDP_FUNC_ID_DEVICE_DESTROY, &pv) == VDP_STATUS_OK) {
444            VdpDeviceDestroy *device_destroy = pv;
445
446            device_destroy(*device);
447        }
448    }
449
450    return status;
451}
452