1 /*  RetroArch - A frontend for libretro.
2 *  Copyright (C) 2010-2014 - Hans-Kristian Arntzen
3 *  Copyright (C) 2011-2017 - Daniel De Matteis
4 *
5 *  RetroArch is free software: you can redistribute it and/or modify it under the terms
6 *  of the GNU General Public License as published by the Free Software Found-
7 *  ation, either version 3 of the License, or (at your option) any later version.
8 *
9 *  RetroArch is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
10 *  without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
11 *  PURPOSE.  See the GNU General Public License for more details.
12 *
13 *  You should have received a copy of the GNU General Public License along with RetroArch.
14 *  If not, see <http://www.gnu.org/licenses/>.
15 */
16 
17 /* X/EGL context. Mostly used for testing GLES code paths. */
18 
19 #include <stdint.h>
20 #include <stdlib.h>
21 
22 #include <string/stdstring.h>
23 
24 #ifdef HAVE_CONFIG_H
25 #include "../../config.h"
26 #endif
27 
28 #include "../../frontend/frontend_driver.h"
29 #include "../../configuration.h"
30 #include "../../input/input_driver.h"
31 #include "../../verbosity.h"
32 
33 #include "../common/egl_common.h"
34 #include "../common/gl_common.h"
35 #include "../common/x11_common.h"
36 
37 #ifdef HAVE_XINERAMA
38 #include "../common/xinerama_common.h"
39 #endif
40 
41 #ifndef EGL_OPENGL_ES3_BIT_KHR
42 #define EGL_OPENGL_ES3_BIT_KHR 0x0040
43 #endif
44 
45 #ifndef EGL_PLATFORM_X11_KHR
46 #define EGL_PLATFORM_X11_KHR 0x31D5
47 #endif
48 
49 typedef struct
50 {
51 #ifdef HAVE_EGL
52    egl_ctx_data_t egl;
53 #endif
54    bool should_reset_mode;
55 } xegl_ctx_data_t;
56 
57 /* TODO/FIXME - static globals */
58 static enum gfx_ctx_api xegl_api = GFX_CTX_NONE;
59 
xegl_nul_handler(Display * dpy,XErrorEvent * event)60 static int xegl_nul_handler(Display *dpy, XErrorEvent *event) { return 0; }
61 
gfx_ctx_xegl_destroy(void * data)62 static void gfx_ctx_xegl_destroy(void *data)
63 {
64    xegl_ctx_data_t *xegl = (xegl_ctx_data_t*)data;
65 
66    x11_input_ctx_destroy();
67 #ifdef HAVE_EGL
68    egl_destroy(&xegl->egl);
69 #endif
70 
71    if (g_x11_win)
72    {
73 #ifdef HAVE_XINERAMA
74       /* Save last used monitor for later. */
75       xinerama_save_last_used_monitor(RootWindow(
76                g_x11_dpy, DefaultScreen(g_x11_dpy)));
77 #endif
78       x11_window_destroy(false);
79    }
80 
81    x11_colormap_destroy();
82 
83    if (xegl->should_reset_mode)
84    {
85       x11_exit_fullscreen(g_x11_dpy);
86       xegl->should_reset_mode = false;
87    }
88 
89    free(data);
90 
91    /* Do not close g_x11_dpy. We'll keep one for the entire application
92     * lifecycle to work-around nVidia EGL limitations.
93     */
94 }
95 
96 #define XEGL_ATTRIBS_BASE \
97 EGL_SURFACE_TYPE,    EGL_WINDOW_BIT, \
98 EGL_RED_SIZE,        1, \
99 EGL_GREEN_SIZE,      1, \
100 EGL_BLUE_SIZE,       1, \
101 EGL_ALPHA_SIZE,      0, \
102 EGL_DEPTH_SIZE,      0
103 
gfx_ctx_xegl_init(void * video_driver)104 static void *gfx_ctx_xegl_init(void *video_driver)
105 {
106 #ifdef HAVE_EGL
107    static const EGLint egl_attribs_gl[] = {
108       XEGL_ATTRIBS_BASE,
109       EGL_RENDERABLE_TYPE, EGL_OPENGL_BIT,
110       EGL_NONE,
111    };
112 
113    static const EGLint egl_attribs_gles[] = {
114       XEGL_ATTRIBS_BASE,
115       EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
116       EGL_NONE,
117    };
118 
119 #ifdef EGL_KHR_create_context
120    static const EGLint egl_attribs_gles3[] = {
121       XEGL_ATTRIBS_BASE,
122       EGL_RENDERABLE_TYPE, EGL_OPENGL_ES3_BIT_KHR,
123       EGL_NONE,
124    };
125 #endif
126 #ifdef HAVE_VG
127    static const EGLint egl_attribs_vg[] = {
128       XEGL_ATTRIBS_BASE,
129       EGL_RENDERABLE_TYPE, EGL_OPENVG_BIT,
130       EGL_NONE,
131    };
132 #endif
133    const EGLint *attrib_ptr = NULL;
134    EGLint major, minor;
135    EGLint n;
136 #endif
137    xegl_ctx_data_t *xegl;
138 
139    if (g_egl_inited)
140       return NULL;
141 
142    XInitThreads();
143 
144    xegl = (xegl_ctx_data_t*)calloc(1, sizeof(xegl_ctx_data_t));
145    if (!xegl)
146       return NULL;
147 
148    switch (xegl_api)
149    {
150       case GFX_CTX_OPENGL_API:
151          attrib_ptr = egl_attribs_gl;
152          break;
153       case GFX_CTX_OPENGL_ES_API:
154 #ifdef EGL_KHR_create_context
155          if (xegl->egl.major >= 3)
156             attrib_ptr = egl_attribs_gles3;
157          else
158 #endif
159             attrib_ptr = egl_attribs_gles;
160          break;
161       case GFX_CTX_OPENVG_API:
162 #ifdef HAVE_VG
163          attrib_ptr = egl_attribs_vg;
164 #endif
165          break;
166       default:
167          break;
168    }
169 
170    if (!x11_connect())
171       goto error;
172 
173 #ifdef HAVE_EGL
174    if (!egl_init_context(&xegl->egl, EGL_PLATFORM_X11_KHR,
175             (EGLNativeDisplayType)g_x11_dpy, &major, &minor, &n, attrib_ptr, egl_default_accept_config_cb))
176    {
177       egl_report_error();
178       goto error;
179    }
180 
181    if (n == 0 || !egl_has_config(&xegl->egl))
182       goto error;
183 #endif
184 
185    return xegl;
186 
187 error:
188    gfx_ctx_xegl_destroy(xegl);
189    return NULL;
190 }
191 
xegl_fill_attribs(xegl_ctx_data_t * xegl,EGLint * attr)192 static EGLint *xegl_fill_attribs(xegl_ctx_data_t *xegl, EGLint *attr)
193 {
194    switch (xegl_api)
195    {
196 #ifdef EGL_KHR_create_context
197       case GFX_CTX_OPENGL_API:
198          {
199             unsigned version = xegl->egl.major * 1000 + xegl->egl.minor;
200             bool core        = version >= 3001;
201 #ifdef GL_DEBUG
202             bool debug       = true;
203 #else
204             struct retro_hw_render_callback *hwr = video_driver_get_hw_context();
205             bool debug       = hwr->debug_context;
206 #endif
207 
208             if (core)
209             {
210                *attr++ = EGL_CONTEXT_MAJOR_VERSION_KHR;
211                *attr++ = xegl->egl.major;
212                *attr++ = EGL_CONTEXT_MINOR_VERSION_KHR;
213                *attr++ = xegl->egl.minor;
214 
215                /* Technically, we don't have core/compat until 3.2.
216                 * Version 3.1 is either compat or not depending
217                 * on GL_ARB_compatibility.
218                 */
219                if (version >= 3002)
220                {
221                   *attr++ = EGL_CONTEXT_OPENGL_PROFILE_MASK_KHR;
222                   *attr++ = EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT_KHR;
223                }
224             }
225 
226             if (debug)
227             {
228                *attr++ = EGL_CONTEXT_FLAGS_KHR;
229                *attr++ = EGL_CONTEXT_OPENGL_DEBUG_BIT_KHR;
230             }
231 
232             break;
233          }
234 #endif
235 
236       case GFX_CTX_OPENGL_ES_API:
237          /* Same as EGL_CONTEXT_MAJOR_VERSION. */
238          *attr++ = EGL_CONTEXT_CLIENT_VERSION;
239          *attr++ = xegl->egl.major ? (EGLint)xegl->egl.major : 2;
240 #ifdef EGL_KHR_create_context
241          if (xegl->egl.minor > 0)
242          {
243             *attr++ = EGL_CONTEXT_MINOR_VERSION_KHR;
244             *attr++ = xegl->egl.minor;
245          }
246 #endif
247          break;
248 
249       default:
250          break;
251    }
252 
253    *attr = EGL_NONE;
254    return attr;
255 }
256 
257 /* forward declaration */
258 static void gfx_ctx_xegl_set_swap_interval(void *data, int swap_interval);
259 
gfx_ctx_xegl_set_video_mode(void * data,unsigned width,unsigned height,bool fullscreen)260 static bool gfx_ctx_xegl_set_video_mode(void *data,
261       unsigned width, unsigned height,
262       bool fullscreen)
263 {
264    XEvent event;
265    EGLint egl_attribs[16];
266    EGLint vid, num_visuals;
267    EGLint *attr                   = NULL;
268    bool true_full                 = false;
269    int x_off                      = 0;
270    int y_off                      = 0;
271    XVisualInfo temp               = {0};
272    XSetWindowAttributes swa       = {0};
273    XVisualInfo *vi                = NULL;
274    char *wm_name                  = NULL;
275    xegl_ctx_data_t *xegl          = (xegl_ctx_data_t*)data;
276    settings_t *settings           = config_get_ptr();
277    bool video_disable_composition = settings->bools.video_disable_composition;
278    bool windowed_fullscreen       = settings->bools.video_windowed_fullscreen;
279    unsigned video_monitor_index   = settings->uints.video_monitor_index;
280 
281    int (*old_handler)(Display*, XErrorEvent*) = NULL;
282 
283    frontend_driver_install_signal_handler();
284 
285    attr = egl_attribs;
286    attr = xegl_fill_attribs(xegl, attr);
287 
288 #ifdef HAVE_EGL
289    if (!egl_get_native_visual_id(&xegl->egl, &vid))
290       goto error;
291 #endif
292 
293    temp.visualid = vid;
294 
295    vi = XGetVisualInfo(g_x11_dpy, VisualIDMask, &temp, &num_visuals);
296    if (!vi)
297       goto error;
298 
299    swa.colormap = g_x11_cmap = XCreateColormap(
300          g_x11_dpy, RootWindow(g_x11_dpy, vi->screen),
301          vi->visual, AllocNone);
302    swa.event_mask = StructureNotifyMask | KeyPressMask |
303       ButtonPressMask | ButtonReleaseMask | KeyReleaseMask |
304       EnterWindowMask | LeaveWindowMask;
305    swa.override_redirect = False;
306 
307    if (fullscreen && !windowed_fullscreen)
308    {
309       if (x11_enter_fullscreen(g_x11_dpy, width, height))
310       {
311          xegl->should_reset_mode = true;
312          true_full = true;
313       }
314       else
315          RARCH_ERR("[X/EGL]: Entering true fullscreen failed. Will attempt windowed mode.\n");
316    }
317 
318    wm_name = x11_get_wm_name(g_x11_dpy);
319    if (wm_name)
320    {
321       RARCH_LOG("[X/EGL]: Window manager is %s.\n", wm_name);
322 
323       if (true_full && strcasestr(wm_name, "xfwm"))
324       {
325          RARCH_LOG("[X/EGL]: Using override-redirect workaround.\n");
326          swa.override_redirect = True;
327       }
328       free(wm_name);
329    }
330    if (!x11_has_net_wm_fullscreen(g_x11_dpy) && true_full)
331       swa.override_redirect = True;
332 
333    if (video_monitor_index)
334       g_x11_screen = video_monitor_index - 1;
335 
336 #ifdef HAVE_XINERAMA
337    if (fullscreen || g_x11_screen != 0)
338    {
339       unsigned new_width  = width;
340       unsigned new_height = height;
341 
342       if (xinerama_get_coord(g_x11_dpy, g_x11_screen,
343                &x_off, &y_off, &new_width, &new_height))
344          RARCH_LOG("[X/EGL]: Using Xinerama on screen #%u.\n", g_x11_screen);
345       else
346          RARCH_LOG("[X/EGL]: Xinerama is not active on screen.\n");
347 
348       if (fullscreen)
349       {
350          width  = new_width;
351          height = new_height;
352       }
353    }
354 #endif
355 
356    RARCH_LOG("[X/EGL]: X = %d, Y = %d, W = %u, H = %u.\n",
357          x_off, y_off, width, height);
358 
359    g_x11_win = XCreateWindow(g_x11_dpy, RootWindow(g_x11_dpy, vi->screen),
360          x_off, y_off, width, height, 0,
361          vi->depth, InputOutput, vi->visual,
362          CWBorderPixel | CWColormap | CWEventMask | CWOverrideRedirect,
363          &swa);
364    XSetWindowBackground(g_x11_dpy, g_x11_win, 0);
365 
366    if (fullscreen && video_disable_composition)
367    {
368       uint32_t value                = 1;
369       Atom cardinal                 = XInternAtom(g_x11_dpy, "CARDINAL", False);
370       Atom net_wm_bypass_compositor = XInternAtom(g_x11_dpy, "_NET_WM_BYPASS_COMPOSITOR", False);
371 
372       RARCH_LOG("[X/EGL]: Requesting compositor bypass.\n");
373       XChangeProperty(g_x11_dpy, g_x11_win, net_wm_bypass_compositor, cardinal, 32, PropModeReplace, (const unsigned char*)&value, 1);
374    }
375 
376    if (!egl_create_context(&xegl->egl, (attr != egl_attribs) ? egl_attribs : NULL))
377    {
378       egl_report_error();
379       goto error;
380    }
381 
382    if (!egl_create_surface(&xegl->egl, (void*)g_x11_win))
383       goto error;
384 
385    x11_set_window_attr(g_x11_dpy, g_x11_win);
386    x11_update_title(NULL);
387 
388    if (fullscreen)
389       x11_show_mouse(g_x11_dpy, g_x11_win, false);
390 
391    if (true_full)
392    {
393       RARCH_LOG("[X/EGL]: Using true fullscreen.\n");
394       XMapRaised(g_x11_dpy, g_x11_win);
395       x11_set_net_wm_fullscreen(g_x11_dpy, g_x11_win);
396    }
397    else if (fullscreen)
398    {
399       /* We attempted true fullscreen, but failed.
400        * Attempt using windowed fullscreen. */
401       XMapRaised(g_x11_dpy, g_x11_win);
402       RARCH_LOG("[X/EGL]: Using windowed fullscreen.\n");
403 
404       /* We have to move the window to the screen we
405        * want to go fullscreen on first.
406        * x_off and y_off usually get ignored in XCreateWindow().
407        */
408       x11_move_window(g_x11_dpy, g_x11_win, x_off, y_off, width, height);
409       x11_set_net_wm_fullscreen(g_x11_dpy, g_x11_win);
410    }
411    else
412    {
413       XMapWindow(g_x11_dpy, g_x11_win);
414 
415       /* If we want to map the window on a different screen,
416        * we'll have to do it by force.
417        *
418        * Otherwise, we should try to let the window manager sort it out.
419        * x_off and y_off usually get ignored in XCreateWindow().
420        */
421       if (g_x11_screen)
422          x11_move_window(g_x11_dpy, g_x11_win, x_off, y_off, width, height);
423    }
424 
425    x11_event_queue_check(&event);
426    x11_install_quit_atom();
427 
428 #ifdef HAVE_EGL
429    gfx_ctx_xegl_set_swap_interval(&xegl->egl, xegl->egl.interval);
430 #endif
431 
432    /* This can blow up on some drivers. It's not fatal,
433     * so override errors for this call.
434     */
435    old_handler = XSetErrorHandler(xegl_nul_handler);
436    XSetInputFocus(g_x11_dpy, g_x11_win, RevertToNone, CurrentTime);
437    XSync(g_x11_dpy, False);
438    XSetErrorHandler(old_handler);
439 
440    XFree(vi);
441    g_egl_inited = true;
442 
443    if (!x11_input_ctx_new(true_full))
444       goto error;
445 
446    return true;
447 
448 error:
449    if (vi)
450       XFree(vi);
451 
452    gfx_ctx_xegl_destroy(data);
453    return false;
454 }
455 
gfx_ctx_xegl_input_driver(void * data,const char * joypad_name,input_driver_t ** input,void ** input_data)456 static void gfx_ctx_xegl_input_driver(void *data,
457       const char *joypad_name,
458       input_driver_t **input, void **input_data)
459 {
460    void *xinput = input_driver_init_wrap(&input_x, joypad_name);
461 
462    *input       = xinput ? &input_x : NULL;
463    *input_data  = xinput;
464 }
465 
gfx_ctx_xegl_suppress_screensaver(void * data,bool enable)466 static bool gfx_ctx_xegl_suppress_screensaver(void *data, bool enable)
467 {
468    if (video_driver_display_type_get() != RARCH_DISPLAY_X11)
469       return false;
470 
471    x11_suspend_screensaver(video_driver_window_get(), enable);
472 
473    return true;
474 }
475 
gfx_ctx_xegl_get_api(void * data)476 static enum gfx_ctx_api gfx_ctx_xegl_get_api(void *data) { return xegl_api; }
477 
gfx_ctx_xegl_bind_api(void * video_driver,enum gfx_ctx_api api,unsigned major,unsigned minor)478 static bool gfx_ctx_xegl_bind_api(void *video_driver,
479    enum gfx_ctx_api api, unsigned major, unsigned minor)
480 {
481    g_egl_major  = major;
482    g_egl_minor  = minor;
483    xegl_api     = api;
484 
485    switch (api)
486    {
487       case GFX_CTX_OPENGL_API:
488 #ifndef EGL_KHR_create_context
489          if ((major * 1000 + minor) >= 3001)
490             break;
491 #endif
492          return egl_bind_api(EGL_OPENGL_API);
493       case GFX_CTX_OPENGL_ES_API:
494 #ifndef EGL_KHR_create_context
495          if (major >= 3)
496             break;
497 #endif
498          return egl_bind_api(EGL_OPENGL_ES_API);
499       case GFX_CTX_OPENVG_API:
500 #ifdef HAVE_VG
501          return egl_bind_api(EGL_OPENVG_API);
502 #endif
503       default:
504          break;
505    }
506 
507    return false;
508 }
509 
gfx_ctx_xegl_show_mouse(void * data,bool state)510 static void gfx_ctx_xegl_show_mouse(void *data, bool state)
511 {
512    x11_show_mouse(g_x11_dpy, g_x11_win, state);
513 }
514 
gfx_ctx_xegl_swap_buffers(void * data)515 static void gfx_ctx_xegl_swap_buffers(void *data)
516 {
517    xegl_ctx_data_t *xegl = (xegl_ctx_data_t*)data;
518 
519 #ifdef HAVE_EGL
520    egl_swap_buffers(&xegl->egl);
521 #endif
522 }
523 
gfx_ctx_xegl_bind_hw_render(void * data,bool enable)524 static void gfx_ctx_xegl_bind_hw_render(void *data, bool enable)
525 {
526    xegl_ctx_data_t *xegl = (xegl_ctx_data_t*)data;
527 
528 #ifdef HAVE_EGL
529    egl_bind_hw_render(&xegl->egl, enable);
530 #endif
531 }
532 
gfx_ctx_xegl_set_swap_interval(void * data,int swap_interval)533 static void gfx_ctx_xegl_set_swap_interval(void *data, int swap_interval)
534 {
535    xegl_ctx_data_t *xegl = (xegl_ctx_data_t*)data;
536 
537 #ifdef HAVE_EGL
538    egl_set_swap_interval(&xegl->egl, swap_interval);
539 #endif
540 }
541 
gfx_ctx_xegl_get_proc_address(const char * symbol)542 static gfx_ctx_proc_t gfx_ctx_xegl_get_proc_address(const char *symbol)
543 {
544    switch (xegl_api)
545    {
546       case GFX_CTX_OPENGL_ES_API:
547       case GFX_CTX_OPENVG_API:
548 #ifdef HAVE_EGL
549          return egl_get_proc_address(symbol);
550 #else
551          break;
552 #endif
553       case GFX_CTX_OPENGL_API:
554          break;
555       case GFX_CTX_NONE:
556       default:
557          break;
558    }
559 
560    return NULL;
561 }
562 
gfx_ctx_xegl_get_flags(void * data)563 static uint32_t gfx_ctx_xegl_get_flags(void *data)
564 {
565    uint32_t flags = 0;
566 
567    if (string_is_equal(video_driver_get_ident(), "glcore"))
568    {
569 #if defined(HAVE_SLANG) && defined(HAVE_SPIRV_CROSS)
570       BIT32_SET(flags, GFX_CTX_FLAGS_SHADERS_SLANG);
571 #endif
572    }
573    else
574    {
575       BIT32_SET(flags, GFX_CTX_FLAGS_SHADERS_GLSL);
576    }
577 
578    return flags;
579 }
580 
gfx_ctx_xegl_set_flags(void * data,uint32_t flags)581 static void gfx_ctx_xegl_set_flags(void *data, uint32_t flags) { }
582 
583 const gfx_ctx_driver_t gfx_ctx_x_egl =
584 {
585    gfx_ctx_xegl_init,
586    gfx_ctx_xegl_destroy,
587    gfx_ctx_xegl_get_api,
588    gfx_ctx_xegl_bind_api,
589    gfx_ctx_xegl_set_swap_interval,
590    gfx_ctx_xegl_set_video_mode,
591    x11_get_video_size,
592    x11_get_refresh_rate,
593    NULL, /* get_video_output_size */
594    NULL, /* get_video_output_prev */
595    NULL, /* get_video_output_next */
596    x11_get_metrics,
597    NULL,
598    x11_update_title,
599    x11_check_window,
600    NULL, /* set_resize */
601    x11_has_focus,
602    gfx_ctx_xegl_suppress_screensaver,
603    true, /* has_windowed */
604    gfx_ctx_xegl_swap_buffers,
605    gfx_ctx_xegl_input_driver,
606    gfx_ctx_xegl_get_proc_address,
607    NULL,
608    NULL,
609    gfx_ctx_xegl_show_mouse,
610    "egl_x",
611    gfx_ctx_xegl_get_flags,
612    gfx_ctx_xegl_set_flags,
613    gfx_ctx_xegl_bind_hw_render,
614    NULL,
615    NULL
616 };
617