1 /*  RetroArch - A frontend for libretro.
2  *  Copyright (C) 2010-2014 - Hans-Kristian Arntzen
3  *  Copyright (C) 2011-2017 - Daniel De Matteis
4  *  Copyright (C) 2016-2019 - Brad Parker
5  *
6  *  RetroArch is free software: you can redistribute it and/or modify it under the terms
7  *  of the GNU General Public License as published by the Free Software Found-
8  *  ation, either version 3 of the License, or (at your option) any later version.
9  *
10  *  RetroArch is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
11  *  without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
12  *  PURPOSE.  See the GNU General Public License for more details.
13  *
14  *  You should have received a copy of the GNU General Public License along with RetroArch.
15  *  If not, see <http://www.gnu.org/licenses/>.
16  */
17 
18 #include <stdint.h>
19 #include <stdlib.h>
20 #include <signal.h>
21 
22 #ifdef HAVE_CONFIG_H
23 #include "../../config.h"
24 #endif
25 
26 #include <string/stdstring.h>
27 #include <compat/strcasestr.h>
28 #include <retro_timers.h>
29 #include <X11/Xatom.h>
30 
31 #include "../../configuration.h"
32 #include "../../frontend/frontend_driver.h"
33 #include "../../input/input_driver.h"
34 #include "../../verbosity.h"
35 #include "../common/gl_common.h"
36 #include "../common/x11_common.h"
37 
38 #ifdef HAVE_XINERAMA
39 #include "../common/xinerama_common.h"
40 #endif
41 
42 #include "../common/vulkan_common.h"
43 
44 typedef struct gfx_ctx_x_vk_data
45 {
46    bool should_reset_mode;
47    bool is_fullscreen;
48 
49    int interval;
50 
51    gfx_ctx_vulkan_data_t vk;
52 } gfx_ctx_x_vk_data_t;
53 
54 typedef struct Hints
55 {
56    unsigned long flags;
57    unsigned long functions;
58    unsigned long decorations;
59    long          inputMode;
60    unsigned long status;
61 } Hints;
62 
63 /* We use long because X11 wants 32-bit pixels for 32-bit systems and 64 for 64... */
64 /* ARGB*/
65 static const unsigned long retroarch_icon_vk_data[] = {
66    16, 16,
67 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
68 0x00000000,0x00000000,0xff333333,0xff333333,0xff333333,0xff333333,0xff333333,0xff333333,0xff333333,0xff333333,0xff333333,0xff333333,0xff333333,0x00000000,0x00000000,0x00000000,
69 0x00000000,0xff333333,0xff333333,0xff333333,0xff333333,0xff333333,0xff333333,0xff333333,0xff333333,0xff333333,0xff333333,0xff333333,0xff333333,0xff333333,0x00000000,0x00000000,
70 0x00000000,0xff333333,0xff333333,0xff333333,0xff333333,0xff333333,0xff333333,0xff333333,0xff333333,0xff333333,0xff333333,0xff333333,0xff333333,0xff333333,0x00000000,0x00000000,
71 0x00000000,0xff333333,0xff333333,0xff333333,0xfff2f2f2,0xff333333,0xff333333,0xff333333,0xff333333,0xff333333,0xfff2f2f2,0xff333333,0xff333333,0xff333333,0x00000000,0x00000000,
72 0x00000000,0xff333333,0xfff2f2f2,0xff333333,0xff333333,0xfff2f2f2,0xff333333,0xff333333,0xff333333,0xfff2f2f2,0xff333333,0xff333333,0xfff2f2f2,0xff333333,0x00000000,0x00000000,
73 0x00000000,0xff333333,0xfff2f2f2,0xff333333,0xfff2f2f2,0xfff2f2f2,0xfff2f2f2,0xfff2f2f2,0xfff2f2f2,0xfff2f2f2,0xfff2f2f2,0xff333333,0xfff2f2f2,0xff333333,0x00000000,0x00000000,
74 0x00000000,0xff333333,0xfff2f2f2,0xfff2f2f2,0xfff2f2f2,0xff333333,0xfff2f2f2,0xfff2f2f2,0xfff2f2f2,0xff333333,0xfff2f2f2,0xfff2f2f2,0xfff2f2f2,0xff333333,0x00000000,0x00000000,
75 0x00000000,0xff333333,0xfff2f2f2,0xfff2f2f2,0xfff2f2f2,0xfff2f2f2,0xfff2f2f2,0xfff2f2f2,0xfff2f2f2,0xfff2f2f2,0xfff2f2f2,0xfff2f2f2,0xfff2f2f2,0xff333333,0x00000000,0x00000000,
76 0x00000000,0xff333333,0xff333333,0xfff2f2f2,0xfff2f2f2,0xfff2f2f2,0xfff2f2f2,0xfff2f2f2,0xfff2f2f2,0xfff2f2f2,0xfff2f2f2,0xfff2f2f2,0xff333333,0xff333333,0x00000000,0x00000000,
77 0x00000000,0xff333333,0xff333333,0xff333333,0xfff2f2f2,0xff333333,0xff333333,0xff333333,0xff333333,0xff333333,0xfff2f2f2,0xff333333,0xff333333,0xff333333,0x00000000,0x00000000,
78 0x00000000,0xff333333,0xff333333,0xfff2f2f2,0xff333333,0xff333333,0xff333333,0xff333333,0xff333333,0xff333333,0xff333333,0xfff2f2f2,0xff333333,0xff333333,0x00000000,0x00000000,
79 0x00000000,0xff333333,0xff333333,0xff333333,0xff333333,0xff333333,0xff333333,0xff333333,0xff333333,0xff333333,0xff333333,0xff333333,0xff333333,0xff333333,0x00000000,0x00000000,
80 0x00000000,0xff333333,0xff333333,0xff333333,0xff333333,0xff333333,0xff333333,0xff333333,0xff333333,0xff333333,0xff333333,0xff333333,0xff333333,0xff333333,0x00000000,0x00000000,
81 0x00000000,0x00000000,0xff333333,0xff333333,0xff333333,0xff333333,0xff333333,0xff333333,0xff333333,0xff333333,0xff333333,0xff333333,0xff333333,0x00000000,0x00000000,0x00000000,
82 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000
83 };
84 
x_vk_nul_handler(Display * dpy,XErrorEvent * event)85 static int x_vk_nul_handler(Display *dpy, XErrorEvent *event) { return 0; }
86 
gfx_ctx_x_vk_destroy_resources(gfx_ctx_x_vk_data_t * x)87 static void gfx_ctx_x_vk_destroy_resources(gfx_ctx_x_vk_data_t *x)
88 {
89    x11_input_ctx_destroy();
90 
91    if (g_x11_dpy)
92    {
93       vulkan_context_destroy(&x->vk, g_x11_win != 0);
94    }
95 
96    if (g_x11_win && g_x11_dpy)
97    {
98 #ifdef HAVE_XINERAMA
99       /* Save last used monitor for later. */
100       xinerama_save_last_used_monitor(DefaultRootWindow(g_x11_dpy));
101 #endif
102       x11_window_destroy(false);
103    }
104 
105    x11_colormap_destroy();
106 
107    if (g_x11_dpy)
108    {
109       if (x->should_reset_mode)
110       {
111          x11_exit_fullscreen(g_x11_dpy);
112          x->should_reset_mode = false;
113       }
114    }
115 }
116 
gfx_ctx_x_vk_destroy(void * data)117 static void gfx_ctx_x_vk_destroy(void *data)
118 {
119    gfx_ctx_x_vk_data_t *x = (gfx_ctx_x_vk_data_t*)data;
120    if (!x)
121       return;
122 
123    gfx_ctx_x_vk_destroy_resources(x);
124 
125 #if defined(HAVE_THREADS)
126    if (x->vk.context.queue_lock)
127       slock_free(x->vk.context.queue_lock);
128 #endif
129 
130    free(data);
131 }
132 
gfx_ctx_x_vk_swap_interval(void * data,int interval)133 static void gfx_ctx_x_vk_swap_interval(void *data, int interval)
134 {
135    gfx_ctx_x_vk_data_t *x = (gfx_ctx_x_vk_data_t*)data;
136 
137    if (x->interval != interval)
138    {
139       x->interval = interval;
140       if (x->vk.swapchain)
141          x->vk.need_new_swapchain = true;
142    }
143 }
144 
gfx_ctx_x_vk_swap_buffers(void * data)145 static void gfx_ctx_x_vk_swap_buffers(void *data)
146 {
147    gfx_ctx_x_vk_data_t *x = (gfx_ctx_x_vk_data_t*)data;
148 
149    if (x->vk.context.has_acquired_swapchain)
150    {
151       x->vk.context.has_acquired_swapchain = false;
152       if (x->vk.swapchain == VK_NULL_HANDLE)
153       {
154          retro_sleep(10);
155       }
156       else
157          vulkan_present(&x->vk, x->vk.context.current_swapchain_index);
158    }
159    vulkan_acquire_next_image(&x->vk);
160 }
161 
gfx_ctx_x_vk_check_window(void * data,bool * quit,bool * resize,unsigned * width,unsigned * height)162 static void gfx_ctx_x_vk_check_window(void *data, bool *quit,
163       bool *resize, unsigned *width, unsigned *height)
164 {
165    gfx_ctx_x_vk_data_t *x = (gfx_ctx_x_vk_data_t*)data;
166    x11_check_window(data, quit, resize, width, height);
167 
168    if (x->vk.need_new_swapchain)
169       *resize = true;
170 }
171 
gfx_ctx_x_vk_set_resize(void * data,unsigned width,unsigned height)172 static bool gfx_ctx_x_vk_set_resize(void *data,
173       unsigned width, unsigned height)
174 {
175    gfx_ctx_x_vk_data_t *x = (gfx_ctx_x_vk_data_t*)data;
176 
177    if (!x)
178       return false;
179 
180    /*
181     * X11 loses focus on monitor/resolution swap and exits fullscreen.
182     * Set window on top again to maintain both fullscreen and resolution.
183     */
184    if (x->is_fullscreen) {
185       XMapRaised(g_x11_dpy, g_x11_win);
186       RARCH_LOG("[X/Vulkan]: Resized fullscreen resolution to %dx%d.\n", width, height);
187    }
188 
189    /* FIXME/TODO - threading error here */
190 
191    if (!vulkan_create_swapchain(&x->vk, width, height, x->interval))
192    {
193       RARCH_ERR("[X/Vulkan]: Failed to update swapchain.\n");
194       x->vk.swapchain = VK_NULL_HANDLE;
195       return false;
196    }
197 
198    if (x->vk.created_new_swapchain)
199       vulkan_acquire_next_image(&x->vk);
200    x->vk.context.invalid_swapchain = true;
201    x->vk.need_new_swapchain        = false;
202    return true;
203 }
204 
gfx_ctx_x_vk_init(void * data)205 static void *gfx_ctx_x_vk_init(void *data)
206 {
207    int nelements           = 0;
208    int major               = 0;
209    int minor               = 0;
210    gfx_ctx_x_vk_data_t *x = (gfx_ctx_x_vk_data_t*)
211       calloc(1, sizeof(gfx_ctx_x_vk_data_t));
212 
213    if (!x)
214       return NULL;
215 
216    XInitThreads();
217 
218    if (!x11_connect())
219       goto error;
220 
221    /* Use XCB WSI since it's the most supported WSI over legacy Xlib. */
222    if (!vulkan_context_init(&x->vk, VULKAN_WSI_XCB))
223       goto error;
224 
225    return x;
226 
227 error:
228    if (x)
229    {
230       gfx_ctx_x_vk_destroy_resources(x);
231       free(x);
232    }
233    g_x11_screen = 0;
234 
235    return NULL;
236 }
237 
gfx_ctx_x_vk_set_video_mode(void * data,unsigned width,unsigned height,bool fullscreen)238 static bool gfx_ctx_x_vk_set_video_mode(void *data,
239       unsigned width, unsigned height,
240       bool fullscreen)
241 {
242    XEvent event;
243    bool true_full            = false;
244    int val                   = 0;
245    int x_off                 = 0;
246    int y_off                 = 0;
247    XVisualInfo *vi           = NULL;
248    XSetWindowAttributes swa  = {0};
249    char *wm_name             = NULL;
250    int (*old_handler)(Display*, XErrorEvent*) = NULL;
251    gfx_ctx_x_vk_data_t *x    = (gfx_ctx_x_vk_data_t*)data;
252    Atom net_wm_icon          = XInternAtom(g_x11_dpy, "_NET_WM_ICON", False);
253    Atom cardinal             = XInternAtom(g_x11_dpy, "CARDINAL", False);
254    settings_t *settings      = config_get_ptr();
255    unsigned opacity          = settings->uints.video_window_opacity
256       * ((unsigned)-1 / 100.0);
257    bool disable_composition  = settings->bools.video_disable_composition;
258    bool show_decorations     = settings->bools.video_window_show_decorations;
259    bool windowed_full        = settings->bools.video_windowed_fullscreen;
260    unsigned video_monitor_index = settings->uints.video_monitor_index;
261 
262    frontend_driver_install_signal_handler();
263 
264    if (!x)
265       return false;
266 
267    {
268       XVisualInfo vi_template;
269       /* For default case, just try to obtain a visual from template. */
270       int nvisuals = 0;
271 
272       memset(&vi_template, 0, sizeof(vi_template));
273       vi_template.screen = DefaultScreen(g_x11_dpy);
274       vi = XGetVisualInfo(g_x11_dpy, VisualScreenMask, &vi_template, &nvisuals);
275       if (!vi || nvisuals < 1)
276          goto error;
277    }
278 
279    swa.colormap = g_x11_cmap = XCreateColormap(g_x11_dpy,
280          RootWindow(g_x11_dpy, vi->screen), vi->visual, AllocNone);
281    swa.event_mask = StructureNotifyMask | KeyPressMask | KeyReleaseMask |
282       LeaveWindowMask | EnterWindowMask |
283       ButtonReleaseMask | ButtonPressMask;
284    swa.override_redirect = False;
285 
286    x->is_fullscreen = fullscreen;
287 
288    if (fullscreen && !windowed_full)
289    {
290       if (x11_enter_fullscreen(g_x11_dpy, width, height))
291       {
292          x->should_reset_mode = true;
293          true_full = true;
294       }
295       else
296          RARCH_ERR("[X/Vulkan]: Entering true fullscreen failed. Will attempt windowed mode.\n");
297    }
298 
299    wm_name = x11_get_wm_name(g_x11_dpy);
300    if (wm_name)
301    {
302       RARCH_LOG("[X/Vulkan]: Window manager is %s.\n", wm_name);
303 
304       if (true_full && strcasestr(wm_name, "xfwm"))
305       {
306          RARCH_LOG("[X/Vulkan]: Using override-redirect workaround.\n");
307          swa.override_redirect = True;
308       }
309       free(wm_name);
310    }
311    if (!x11_has_net_wm_fullscreen(g_x11_dpy) && true_full)
312       swa.override_redirect = True;
313 
314    if (video_monitor_index)
315       g_x11_screen = video_monitor_index - 1;
316 
317 #ifdef HAVE_XINERAMA
318    if (fullscreen || g_x11_screen != 0)
319    {
320       unsigned new_width  = width;
321       unsigned new_height = height;
322 
323       if (xinerama_get_coord(g_x11_dpy, g_x11_screen,
324                &x_off, &y_off, &new_width, &new_height))
325          RARCH_LOG("[X/Vulkan]: Using Xinerama on screen #%u.\n", g_x11_screen);
326       else
327          RARCH_LOG("[X/Vulkan]: Xinerama is not active on screen.\n");
328 
329       if (fullscreen)
330       {
331          width  = new_width;
332          height = new_height;
333       }
334    }
335 #endif
336 
337    RARCH_LOG("[X/Vulkan]: X = %d, Y = %d, W = %u, H = %u.\n",
338          x_off, y_off, width, height);
339 
340    g_x11_win = XCreateWindow(g_x11_dpy, RootWindow(g_x11_dpy, vi->screen),
341          x_off, y_off, width, height, 0,
342          vi->depth, InputOutput, vi->visual,
343          CWBorderPixel | CWColormap | CWEventMask | CWOverrideRedirect,
344          &swa);
345    XSetWindowBackground(g_x11_dpy, g_x11_win, 0);
346 
347    XChangeProperty(g_x11_dpy, g_x11_win, net_wm_icon, cardinal, 32, PropModeReplace, (const unsigned char*)retroarch_icon_vk_data, sizeof(retroarch_icon_vk_data) / sizeof(*retroarch_icon_vk_data));
348 
349    if (fullscreen && disable_composition)
350    {
351       uint32_t                value = 1;
352       Atom net_wm_bypass_compositor = XInternAtom(g_x11_dpy, "_NET_WM_BYPASS_COMPOSITOR", False);
353 
354       RARCH_LOG("[X/Vulkan]: Requesting compositor bypass.\n");
355       XChangeProperty(g_x11_dpy, g_x11_win, net_wm_bypass_compositor, cardinal, 32, PropModeReplace, (const unsigned char*)&value, 1);
356    }
357 
358    if (opacity < (unsigned)-1)
359    {
360       Atom net_wm_opacity = XInternAtom(g_x11_dpy, "_NET_WM_WINDOW_OPACITY", False);
361       XChangeProperty(g_x11_dpy, g_x11_win, net_wm_opacity, cardinal, 32, PropModeReplace, (const unsigned char*)&opacity, 1);
362    }
363 
364    if (!show_decorations)
365    {
366       /* We could have just set _NET_WM_WINDOW_TYPE_DOCK instead,
367        * but that removes the window from any taskbar/panel,
368        * so we are forced to use the old motif hints method. */
369       Hints hints;
370       Atom property     = XInternAtom(g_x11_dpy, "_MOTIF_WM_HINTS", False);
371 
372       hints.flags       = 2;
373       hints.decorations = 0;
374 
375       XChangeProperty(g_x11_dpy, g_x11_win, property, property, 32, PropModeReplace, (const unsigned char*)&hints, 5);
376    }
377 
378    x11_set_window_attr(g_x11_dpy, g_x11_win);
379    x11_update_title(NULL);
380 
381    if (fullscreen)
382       x11_show_mouse(g_x11_dpy, g_x11_win, false);
383 
384    if (true_full)
385    {
386       RARCH_LOG("[X/Vulkan]: Using true fullscreen.\n");
387       XMapRaised(g_x11_dpy, g_x11_win);
388       x11_set_net_wm_fullscreen(g_x11_dpy, g_x11_win);
389    }
390    else if (fullscreen)
391    {
392       /* We attempted true fullscreen, but failed.
393        * Attempt using windowed fullscreen. */
394 
395       XMapRaised(g_x11_dpy, g_x11_win);
396       RARCH_LOG("[X/Vulkan]: Using windowed fullscreen.\n");
397 
398       /* We have to move the window to the screen we want
399        * to go fullscreen on first.
400        * x_off and y_off usually get ignored in XCreateWindow().
401        */
402       x11_move_window(g_x11_dpy, g_x11_win, x_off, y_off, width, height);
403       x11_set_net_wm_fullscreen(g_x11_dpy, g_x11_win);
404    }
405    else
406    {
407       XMapWindow(g_x11_dpy, g_x11_win);
408       /* If we want to map the window on a different screen,
409        * we'll have to do it by force.
410        * Otherwise, we should try to let the window manager sort it out.
411        * x_off and y_off usually get ignored in XCreateWindow(). */
412       if (g_x11_screen)
413          x11_move_window(g_x11_dpy, g_x11_win, x_off, y_off, width, height);
414    }
415 
416    x11_event_queue_check(&event);
417 
418    {
419       bool quit, resize;
420       unsigned width = 0, height = 0;
421       x11_check_window(x, &quit, &resize, &width, &height);
422 
423       /* FIXME/TODO - threading error here */
424 
425       /* Use XCB surface since it's the most supported WSI.
426        * We can obtain the XCB connection directly from X11. */
427       if (!vulkan_surface_create(&x->vk, VULKAN_WSI_XCB,
428                g_x11_dpy, &g_x11_win,
429                width, height, x->interval))
430          goto error;
431    }
432 
433    XSync(g_x11_dpy, False);
434 
435    x11_install_quit_atom();
436 
437    gfx_ctx_x_vk_swap_interval(data, x->interval);
438 
439    /* This can blow up on some drivers.
440     * It's not fatal, so override errors for this call. */
441    old_handler = XSetErrorHandler(x_vk_nul_handler);
442    XSetInputFocus(g_x11_dpy, g_x11_win, RevertToNone, CurrentTime);
443    XSync(g_x11_dpy, False);
444    XSetErrorHandler(old_handler);
445 
446    XFree(vi);
447    vi = NULL;
448 
449    if (!x11_input_ctx_new(true_full))
450       goto error;
451 
452    return true;
453 
454 error:
455    if (vi)
456       XFree(vi);
457 
458    gfx_ctx_x_vk_destroy_resources(x);
459 
460    if (x)
461       free(x);
462    g_x11_screen = 0;
463 
464    return false;
465 }
466 
gfx_ctx_x_vk_input_driver(void * data,const char * joypad_name,input_driver_t ** input,void ** input_data)467 static void gfx_ctx_x_vk_input_driver(void *data,
468       const char *joypad_name,
469       input_driver_t **input, void **input_data)
470 {
471    void *x_input            = NULL;
472 #ifdef HAVE_UDEV
473    settings_t *settings     = config_get_ptr();
474    const char *input_driver = settings->arrays.input_driver;
475 
476    if (string_is_equal(input_driver, "udev"))
477    {
478       *input_data = input_driver_init_wrap(&input_udev, joypad_name);
479       if (*input_data)
480       {
481          *input = &input_udev;
482          return;
483       }
484    }
485 #endif
486 
487    x_input      = input_driver_init_wrap(&input_x, joypad_name);
488    *input       = x_input ? &input_x : NULL;
489    *input_data  = x_input;
490 }
491 
gfx_ctx_x_vk_suppress_screensaver(void * data,bool enable)492 static bool gfx_ctx_x_vk_suppress_screensaver(void *data, bool enable)
493 {
494    if (video_driver_display_type_get() != RARCH_DISPLAY_X11)
495       return false;
496 
497    x11_suspend_screensaver(video_driver_window_get(), enable);
498 
499    return true;
500 }
501 
gfx_ctx_x_vk_get_api(void * data)502 static enum gfx_ctx_api gfx_ctx_x_vk_get_api(void *data)
503 {
504    return GFX_CTX_VULKAN_API;
505 }
506 
gfx_ctx_x_vk_bind_api(void * data,enum gfx_ctx_api api,unsigned major,unsigned minor)507 static bool gfx_ctx_x_vk_bind_api(void *data, enum gfx_ctx_api api,
508       unsigned major, unsigned minor)
509 {
510    if (api == GFX_CTX_VULKAN_API)
511          return true;
512 
513    return false;
514 }
515 
gfx_ctx_x_vk_show_mouse(void * data,bool state)516 static void gfx_ctx_x_vk_show_mouse(void *data, bool state)
517 {
518    x11_show_mouse(g_x11_dpy, g_x11_win, state);
519 }
520 
gfx_ctx_x_vk_bind_hw_render(void * data,bool enable)521 static void gfx_ctx_x_vk_bind_hw_render(void *data, bool enable)
522 {
523    gfx_ctx_x_vk_data_t *x = (gfx_ctx_x_vk_data_t*)data;
524 
525    if (!x)
526       return;
527 }
528 
gfx_ctx_x_vk_get_context_data(void * data)529 static void *gfx_ctx_x_vk_get_context_data(void *data)
530 {
531    gfx_ctx_x_vk_data_t *x = (gfx_ctx_x_vk_data_t*)data;
532    return &x->vk.context;
533 }
534 
gfx_ctx_x_vk_get_flags(void * data)535 static uint32_t gfx_ctx_x_vk_get_flags(void *data)
536 {
537    uint32_t      flags = 0;
538    gfx_ctx_x_vk_data_t *x = (gfx_ctx_x_vk_data_t*)data;
539 
540 #if defined(HAVE_SLANG) && defined(HAVE_SPIRV_CROSS)
541    BIT32_SET(flags, GFX_CTX_FLAGS_SHADERS_SLANG);
542 #endif
543 
544    return flags;
545 }
546 
gfx_ctx_x_vk_set_flags(void * data,uint32_t flags)547 static void gfx_ctx_x_vk_set_flags(void *data, uint32_t flags) { }
548 
549 const gfx_ctx_driver_t gfx_ctx_vk_x = {
550    gfx_ctx_x_vk_init,
551    gfx_ctx_x_vk_destroy,
552    gfx_ctx_x_vk_get_api,
553    gfx_ctx_x_vk_bind_api,
554    gfx_ctx_x_vk_swap_interval,
555    gfx_ctx_x_vk_set_video_mode,
556    x11_get_video_size,
557    x11_get_refresh_rate,
558    NULL, /* get_video_output_size */
559    NULL, /* get_video_output_prev */
560    NULL, /* get_video_output_next */
561    x11_get_metrics,
562    NULL,
563    x11_update_title,
564    gfx_ctx_x_vk_check_window,
565    gfx_ctx_x_vk_set_resize,
566    x11_has_focus,
567    gfx_ctx_x_vk_suppress_screensaver,
568    true, /* has_windowed */
569    gfx_ctx_x_vk_swap_buffers,
570    gfx_ctx_x_vk_input_driver,
571    NULL, /* get_proc_address */
572    NULL,
573    NULL,
574    gfx_ctx_x_vk_show_mouse,
575    "vk_x",
576    gfx_ctx_x_vk_get_flags,
577    gfx_ctx_x_vk_set_flags,
578 
579    gfx_ctx_x_vk_bind_hw_render,
580    gfx_ctx_x_vk_get_context_data,
581    NULL /* make_current */
582 };
583