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