gl-x11-egl.c 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657
  1. /******************************************************************************
  2. Copyright (C) 2019 by Ivan Avdeev <[email protected]>
  3. This program is free software: you can redistribute it and/or modify
  4. it under the terms of the GNU General Public License as published by
  5. the Free Software Foundation, either version 2 of the License, or
  6. (at your option) any later version.
  7. This program is distributed in the hope that it will be useful,
  8. but WITHOUT ANY WARRANTY; without even the implied warranty of
  9. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  10. GNU General Public License for more details.
  11. You should have received a copy of the GNU General Public License
  12. along with this program. If not, see <http://www.gnu.org/licenses/>.
  13. ******************************************************************************/
  14. /* GL context initialization using EGL instead of GLX
  15. * Which is essential for improved and more performant screen grabbing and
  16. * VA-API feeding techniques.
  17. *
  18. * Note: most of x11-related functionality was taken from gl-x11.c
  19. */
  20. #include <X11/Xlib.h>
  21. #include <X11/Xlib-xcb.h>
  22. #include <xcb/xcb.h>
  23. #include <stdio.h>
  24. #include "gl-x11-egl.h"
  25. #include <glad/glad_egl.h>
  26. typedef EGLDisplay(EGLAPIENTRYP PFNEGLGETPLATFORMDISPLAYEXTPROC)(
  27. EGLenum platform, void *native_display, const EGLint *attrib_list);
  28. static const int ctx_attribs[] = {
  29. #ifdef _DEBUG
  30. EGL_CONTEXT_OPENGL_DEBUG,
  31. EGL_TRUE,
  32. #endif
  33. EGL_CONTEXT_OPENGL_PROFILE_MASK,
  34. EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT,
  35. EGL_CONTEXT_MAJOR_VERSION,
  36. 3,
  37. EGL_CONTEXT_MINOR_VERSION,
  38. 3,
  39. EGL_NONE,
  40. };
  41. static int ctx_pbuffer_attribs[] = {EGL_WIDTH, 2, EGL_HEIGHT, 2, EGL_NONE};
  42. static const EGLint ctx_config_attribs[] = {EGL_STENCIL_SIZE,
  43. 0,
  44. EGL_DEPTH_SIZE,
  45. 0,
  46. EGL_BUFFER_SIZE,
  47. 32,
  48. EGL_ALPHA_SIZE,
  49. 8,
  50. EGL_RENDERABLE_TYPE,
  51. EGL_OPENGL_BIT,
  52. EGL_SURFACE_TYPE,
  53. EGL_WINDOW_BIT | EGL_PBUFFER_BIT,
  54. EGL_NONE};
  55. struct gl_windowinfo {
  56. EGLConfig config;
  57. /* Windows in X11 are defined with integers (XID).
  58. * xcb_window_t is a define for this... they are
  59. * compatible with Xlib as well.
  60. */
  61. xcb_window_t window;
  62. EGLSurface surface;
  63. /* We can't fetch screen without a request so we cache it. */
  64. int screen;
  65. };
  66. struct gl_platform {
  67. Display *xdisplay;
  68. EGLDisplay edisplay;
  69. EGLConfig config;
  70. EGLContext context;
  71. EGLSurface pbuffer;
  72. };
  73. /* The following utility functions are copied verbatim from GLX code. */
  74. /*
  75. * Since we cannot take advantage of the asynchronous nature of xcb,
  76. * all of the helper functions are synchronous but thread-safe.
  77. *
  78. * They check for errors and will return 0 on problems
  79. * with the exception of when 0 is a valid return value... in which case
  80. * read the specific function comments.
  81. */
  82. /* Returns -1 on invalid screen. */
  83. static int get_screen_num_from_xcb_screen(xcb_connection_t *xcb_conn,
  84. xcb_screen_t *screen)
  85. {
  86. xcb_screen_iterator_t iter =
  87. xcb_setup_roots_iterator(xcb_get_setup(xcb_conn));
  88. int screen_num = 0;
  89. for (; iter.rem; xcb_screen_next(&iter), ++screen_num)
  90. if (iter.data == screen)
  91. return screen_num;
  92. return -1;
  93. }
  94. static xcb_screen_t *get_screen_from_root(xcb_connection_t *xcb_conn,
  95. xcb_window_t root)
  96. {
  97. xcb_screen_iterator_t iter =
  98. xcb_setup_roots_iterator(xcb_get_setup(xcb_conn));
  99. while (iter.rem) {
  100. if (iter.data->root == root)
  101. return iter.data;
  102. xcb_screen_next(&iter);
  103. }
  104. return 0;
  105. }
  106. static inline int get_screen_num_from_root(xcb_connection_t *xcb_conn,
  107. xcb_window_t root)
  108. {
  109. xcb_screen_t *screen = get_screen_from_root(xcb_conn, root);
  110. if (!screen)
  111. return -1;
  112. return get_screen_num_from_xcb_screen(xcb_conn, screen);
  113. }
  114. static xcb_get_geometry_reply_t *get_window_geometry(xcb_connection_t *xcb_conn,
  115. xcb_drawable_t drawable)
  116. {
  117. xcb_get_geometry_cookie_t cookie;
  118. xcb_generic_error_t *error;
  119. xcb_get_geometry_reply_t *reply;
  120. cookie = xcb_get_geometry(xcb_conn, drawable);
  121. reply = xcb_get_geometry_reply(xcb_conn, cookie, &error);
  122. if (error) {
  123. blog(LOG_ERROR, "Failed to fetch parent window geometry!");
  124. free(error);
  125. free(reply);
  126. return 0;
  127. }
  128. free(error);
  129. return reply;
  130. }
  131. static const char *get_egl_error_string2(const EGLint error)
  132. {
  133. switch (error) {
  134. #define OBS_EGL_CASE_ERROR(e) \
  135. case e: \
  136. return #e;
  137. OBS_EGL_CASE_ERROR(EGL_SUCCESS)
  138. OBS_EGL_CASE_ERROR(EGL_NOT_INITIALIZED)
  139. OBS_EGL_CASE_ERROR(EGL_BAD_ACCESS)
  140. OBS_EGL_CASE_ERROR(EGL_BAD_ALLOC)
  141. OBS_EGL_CASE_ERROR(EGL_BAD_ATTRIBUTE)
  142. OBS_EGL_CASE_ERROR(EGL_BAD_CONTEXT)
  143. OBS_EGL_CASE_ERROR(EGL_BAD_CONFIG)
  144. OBS_EGL_CASE_ERROR(EGL_BAD_CURRENT_SURFACE)
  145. OBS_EGL_CASE_ERROR(EGL_BAD_DISPLAY)
  146. OBS_EGL_CASE_ERROR(EGL_BAD_SURFACE)
  147. OBS_EGL_CASE_ERROR(EGL_BAD_MATCH)
  148. OBS_EGL_CASE_ERROR(EGL_BAD_PARAMETER)
  149. OBS_EGL_CASE_ERROR(EGL_BAD_NATIVE_PIXMAP)
  150. OBS_EGL_CASE_ERROR(EGL_BAD_NATIVE_WINDOW)
  151. OBS_EGL_CASE_ERROR(EGL_CONTEXT_LOST)
  152. #undef OBS_EGL_CASE_ERROR
  153. default:
  154. return "Unknown";
  155. }
  156. }
  157. static const char *get_egl_error_string()
  158. {
  159. return get_egl_error_string2(eglGetError());
  160. }
  161. static EGLDisplay get_egl_display(struct gl_platform *plat)
  162. {
  163. Display *display = plat->xdisplay;
  164. EGLDisplay edisplay = EGL_NO_DISPLAY;
  165. const char *egl_client_extensions = NULL;
  166. egl_client_extensions = eglQueryString(EGL_NO_DISPLAY, EGL_EXTENSIONS);
  167. PFNEGLGETPLATFORMDISPLAYEXTPROC eglGetPlatformDisplayEXT =
  168. (PFNEGLGETPLATFORMDISPLAYEXTPROC)(
  169. strstr(egl_client_extensions, "EGL_EXT_platform_base")
  170. ? eglGetProcAddress("eglGetPlatformDisplayEXT")
  171. : NULL);
  172. if (eglGetPlatformDisplayEXT) {
  173. edisplay = eglGetPlatformDisplayEXT(EGL_PLATFORM_X11_EXT,
  174. display, NULL);
  175. if (EGL_NO_DISPLAY == edisplay)
  176. blog(LOG_ERROR, "Failed to get EGL/X11 display");
  177. }
  178. if (EGL_NO_DISPLAY == edisplay)
  179. edisplay = eglGetDisplay(display);
  180. return edisplay;
  181. }
  182. static bool gl_context_create(struct gl_platform *plat)
  183. {
  184. Display *display = plat->xdisplay;
  185. int frame_buf_config_count = 0;
  186. EGLDisplay edisplay = EGL_NO_DISPLAY;
  187. EGLConfig config = NULL;
  188. EGLContext context = EGL_NO_CONTEXT;
  189. int egl_min = 0, egl_maj = 0;
  190. bool success = false;
  191. eglBindAPI(EGL_OPENGL_API);
  192. edisplay = get_egl_display(plat);
  193. if (EGL_NO_DISPLAY == edisplay) {
  194. blog(LOG_ERROR,
  195. "Failed to get EGL display using eglGetDisplay");
  196. return false;
  197. }
  198. if (!eglInitialize(edisplay, &egl_maj, &egl_min)) {
  199. blog(LOG_ERROR, "Failed to initialize EGL: %s",
  200. get_egl_error_string());
  201. return false;
  202. }
  203. if (!eglChooseConfig(edisplay, ctx_config_attribs, &config, 1,
  204. &frame_buf_config_count)) {
  205. blog(LOG_ERROR, "Unable to find suitable EGL config: %s",
  206. get_egl_error_string());
  207. goto error;
  208. }
  209. context =
  210. eglCreateContext(edisplay, config, EGL_NO_CONTEXT, ctx_attribs);
  211. #ifdef _DEBUG
  212. if (EGL_NO_CONTEXT == context) {
  213. const EGLint error = eglGetError();
  214. if (error == EGL_BAD_ATTRIBUTE) {
  215. /* Sometimes creation fails because debug gl is not supported */
  216. blog(LOG_ERROR,
  217. "Unable to create EGL context with DEBUG attrib, trying without");
  218. context = eglCreateContext(edisplay, config,
  219. EGL_NO_CONTEXT,
  220. ctx_attribs + 2);
  221. } else {
  222. blog(LOG_ERROR, "Unable to create EGL context: %s",
  223. get_egl_error_string2(error));
  224. goto error;
  225. }
  226. }
  227. #endif
  228. if (EGL_NO_CONTEXT == context) {
  229. blog(LOG_ERROR, "Unable to create EGL context: %s",
  230. get_egl_error_string());
  231. goto error;
  232. }
  233. plat->pbuffer =
  234. eglCreatePbufferSurface(edisplay, config, ctx_pbuffer_attribs);
  235. if (EGL_NO_SURFACE == plat->pbuffer) {
  236. blog(LOG_ERROR, "Failed to create OpenGL pbuffer: %s",
  237. get_egl_error_string());
  238. goto error;
  239. }
  240. plat->edisplay = edisplay;
  241. plat->config = config;
  242. plat->context = context;
  243. success = true;
  244. blog(LOG_DEBUG, "Created EGLDisplay %p", plat->edisplay);
  245. error:
  246. if (!success) {
  247. if (EGL_NO_CONTEXT != context)
  248. eglDestroyContext(edisplay, context);
  249. eglTerminate(edisplay);
  250. }
  251. XSync(display, false);
  252. return success;
  253. }
  254. static void gl_context_destroy(struct gl_platform *plat)
  255. {
  256. eglMakeCurrent(plat->edisplay, EGL_NO_SURFACE, EGL_NO_SURFACE,
  257. EGL_NO_CONTEXT);
  258. eglDestroyContext(plat->edisplay, plat->context);
  259. }
  260. static struct gl_windowinfo *
  261. gl_x11_egl_windowinfo_create(const struct gs_init_data *info)
  262. {
  263. UNUSED_PARAMETER(info);
  264. return bmalloc(sizeof(struct gl_windowinfo));
  265. }
  266. static void gl_x11_egl_windowinfo_destroy(struct gl_windowinfo *info)
  267. {
  268. UNUSED_PARAMETER(info);
  269. bfree(info);
  270. }
  271. static Display *open_windowless_display(Display *platform_display)
  272. {
  273. Display *display;
  274. xcb_connection_t *xcb_conn;
  275. xcb_screen_iterator_t screen_iterator;
  276. xcb_screen_t *screen;
  277. int screen_num;
  278. if (platform_display)
  279. display = platform_display;
  280. else
  281. display = XOpenDisplay(NULL);
  282. if (!display) {
  283. blog(LOG_ERROR, "Unable to open new X connection!");
  284. return NULL;
  285. }
  286. xcb_conn = XGetXCBConnection(display);
  287. if (!xcb_conn) {
  288. blog(LOG_ERROR, "Unable to get XCB connection to main display");
  289. goto error;
  290. }
  291. screen_iterator = xcb_setup_roots_iterator(xcb_get_setup(xcb_conn));
  292. screen = screen_iterator.data;
  293. if (!screen) {
  294. blog(LOG_ERROR, "Unable to get screen root");
  295. goto error;
  296. }
  297. screen_num = get_screen_num_from_root(xcb_conn, screen->root);
  298. if (screen_num == -1) {
  299. blog(LOG_ERROR, "Unable to get screen number from root");
  300. goto error;
  301. }
  302. if (!gladLoadEGL()) {
  303. blog(LOG_ERROR, "Unable to load EGL entry functions.");
  304. goto error;
  305. }
  306. return display;
  307. error:
  308. if (display)
  309. XCloseDisplay(display);
  310. return NULL;
  311. }
  312. static int x_error_handler(Display *display, XErrorEvent *error)
  313. {
  314. char str1[512];
  315. char str2[512];
  316. char str3[512];
  317. XGetErrorText(display, error->error_code, str1, sizeof(str1));
  318. XGetErrorText(display, error->request_code, str2, sizeof(str2));
  319. XGetErrorText(display, error->minor_code, str3, sizeof(str3));
  320. blog(LOG_ERROR,
  321. "X Error: %s, Major opcode: %s, "
  322. "Minor opcode: %s, Serial: %lu",
  323. str1, str2, str3, error->serial);
  324. return 0;
  325. }
  326. static struct gl_platform *gl_x11_egl_platform_create(gs_device_t *device,
  327. uint32_t adapter)
  328. {
  329. /* There's some trickery here... we're mixing libX11, xcb, and EGL
  330. For an explanation see here: http://xcb.freedesktop.org/MixingCalls/
  331. Essentially, EGL requires Xlib. Everything else we use xcb. */
  332. struct gl_platform *plat = bmalloc(sizeof(struct gl_platform));
  333. Display *platform_display = obs_get_nix_platform_display();
  334. Display *display = open_windowless_display(platform_display);
  335. if (!display) {
  336. goto fail_display_open;
  337. }
  338. XSetEventQueueOwner(display, XCBOwnsEventQueue);
  339. XSetErrorHandler(x_error_handler);
  340. /* We assume later that cur_swap is already set. */
  341. device->plat = plat;
  342. plat->xdisplay = display;
  343. if (!gl_context_create(plat)) {
  344. blog(LOG_ERROR, "Failed to create context!");
  345. goto fail_context_create;
  346. }
  347. if (!eglMakeCurrent(plat->edisplay, plat->pbuffer, plat->pbuffer,
  348. plat->context)) {
  349. blog(LOG_ERROR, "Failed to make context current: %s",
  350. get_egl_error_string());
  351. goto fail_make_current;
  352. }
  353. if (!gladLoadGL()) {
  354. blog(LOG_ERROR, "Failed to load OpenGL entry functions.");
  355. goto fail_load_gl;
  356. }
  357. goto success;
  358. fail_make_current:
  359. gl_context_destroy(plat);
  360. fail_context_create:
  361. fail_load_gl:
  362. XCloseDisplay(display);
  363. fail_display_open:
  364. bfree(plat);
  365. plat = NULL;
  366. success:
  367. UNUSED_PARAMETER(adapter);
  368. return plat;
  369. }
  370. static void gl_x11_egl_platform_destroy(struct gl_platform *plat)
  371. {
  372. if (!plat)
  373. return;
  374. gl_context_destroy(plat);
  375. eglTerminate(plat->edisplay);
  376. bfree(plat);
  377. }
  378. static bool gl_x11_egl_platform_init_swapchain(struct gs_swap_chain *swap)
  379. {
  380. const struct gl_platform *plat = swap->device->plat;
  381. Display *display = plat->xdisplay;
  382. xcb_connection_t *xcb_conn = XGetXCBConnection(display);
  383. xcb_window_t wid = xcb_generate_id(xcb_conn);
  384. xcb_window_t parent = swap->info.window.id;
  385. xcb_get_geometry_reply_t *geometry =
  386. get_window_geometry(xcb_conn, parent);
  387. bool status = false;
  388. int screen_num;
  389. int visual;
  390. if (!geometry)
  391. goto fail_geometry_request;
  392. screen_num = get_screen_num_from_root(xcb_conn, geometry->root);
  393. if (screen_num == -1) {
  394. goto fail_screen;
  395. }
  396. {
  397. if (!eglGetConfigAttrib(plat->edisplay, plat->config,
  398. EGL_NATIVE_VISUAL_ID,
  399. (EGLint *)&visual)) {
  400. blog(LOG_ERROR,
  401. "Cannot get visual id for EGL context: %s",
  402. get_egl_error_string());
  403. goto fail_visual_id;
  404. }
  405. }
  406. xcb_colormap_t colormap = xcb_generate_id(xcb_conn);
  407. uint32_t mask = XCB_CW_BORDER_PIXEL | XCB_CW_COLORMAP;
  408. uint32_t mask_values[] = {0, colormap, 0};
  409. xcb_create_colormap(xcb_conn, XCB_COLORMAP_ALLOC_NONE, colormap, parent,
  410. visual);
  411. xcb_create_window(xcb_conn, 24 /* Hardcoded? */, wid, parent, 0, 0,
  412. geometry->width, geometry->height, 0, 0, visual, mask,
  413. mask_values);
  414. const EGLSurface surface =
  415. eglCreateWindowSurface(plat->edisplay, plat->config, wid, 0);
  416. if (EGL_NO_SURFACE == surface) {
  417. blog(LOG_ERROR, "Cannot get window EGL surface: %s",
  418. get_egl_error_string());
  419. goto fail_window_surface;
  420. }
  421. swap->wi->config = plat->config;
  422. swap->wi->window = wid;
  423. swap->wi->surface = surface;
  424. swap->wi->screen = screen_num;
  425. xcb_map_window(xcb_conn, wid);
  426. status = true;
  427. goto success;
  428. fail_window_surface:
  429. fail_visual_id:
  430. fail_screen:
  431. fail_geometry_request:
  432. success:
  433. free(geometry);
  434. return status;
  435. }
  436. static void gl_x11_egl_platform_cleanup_swapchain(struct gs_swap_chain *swap)
  437. {
  438. UNUSED_PARAMETER(swap);
  439. /* Really nothing to clean up? */
  440. }
  441. static void gl_x11_egl_device_enter_context(gs_device_t *device)
  442. {
  443. const EGLContext context = device->plat->context;
  444. const EGLDisplay display = device->plat->edisplay;
  445. const EGLSurface surface = (device->cur_swap)
  446. ? device->cur_swap->wi->surface
  447. : device->plat->pbuffer;
  448. if (!eglMakeCurrent(display, surface, surface, context))
  449. blog(LOG_ERROR, "Failed to make context current: %s",
  450. get_egl_error_string());
  451. }
  452. static void gl_x11_egl_device_leave_context(gs_device_t *device)
  453. {
  454. const EGLDisplay display = device->plat->edisplay;
  455. if (!eglMakeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE,
  456. EGL_NO_CONTEXT)) {
  457. blog(LOG_ERROR, "Failed to reset current context: %s",
  458. get_egl_error_string());
  459. }
  460. }
  461. static void *gl_x11_egl_device_get_device_obj(gs_device_t *device)
  462. {
  463. return device->plat->context;
  464. }
  465. static void gl_x11_egl_getclientsize(const struct gs_swap_chain *swap,
  466. uint32_t *width, uint32_t *height)
  467. {
  468. xcb_connection_t *xcb_conn =
  469. XGetXCBConnection(swap->device->plat->xdisplay);
  470. xcb_window_t window = swap->wi->window;
  471. xcb_get_geometry_reply_t *geometry =
  472. get_window_geometry(xcb_conn, window);
  473. if (geometry) {
  474. *width = geometry->width;
  475. *height = geometry->height;
  476. }
  477. free(geometry);
  478. }
  479. static void gl_x11_egl_update(gs_device_t *device)
  480. {
  481. Display *display = device->plat->xdisplay;
  482. xcb_window_t window = device->cur_swap->wi->window;
  483. uint32_t values[] = {device->cur_swap->info.cx,
  484. device->cur_swap->info.cy};
  485. xcb_configure_window(XGetXCBConnection(display), window,
  486. XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT,
  487. values);
  488. }
  489. static void gl_x11_egl_clear_context(gs_device_t *device)
  490. {
  491. Display *display = device->plat->edisplay;
  492. if (!eglMakeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE,
  493. EGL_NO_CONTEXT)) {
  494. blog(LOG_ERROR, "Failed to reset current context.");
  495. }
  496. }
  497. static void gl_x11_egl_device_load_swapchain(gs_device_t *device,
  498. gs_swapchain_t *swap)
  499. {
  500. if (device->cur_swap == swap)
  501. return;
  502. device->cur_swap = swap;
  503. device_enter_context(device);
  504. }
  505. enum swap_type {
  506. SWAP_TYPE_NORMAL,
  507. SWAP_TYPE_EXT,
  508. SWAP_TYPE_MESA,
  509. SWAP_TYPE_SGI,
  510. };
  511. static void gl_x11_egl_device_present(gs_device_t *device)
  512. {
  513. Display *display = device->plat->xdisplay;
  514. xcb_connection_t *xcb_conn = XGetXCBConnection(display);
  515. xcb_generic_event_t *xcb_event;
  516. while ((xcb_event = xcb_poll_for_event(xcb_conn))) {
  517. free(xcb_event);
  518. }
  519. if (!eglSwapBuffers(device->plat->edisplay,
  520. device->cur_swap->wi->surface))
  521. blog(LOG_ERROR, "Cannot swap EGL buffers: %s",
  522. get_egl_error_string());
  523. }
  524. static const struct gl_winsys_vtable egl_x11_winsys_vtable = {
  525. .windowinfo_create = gl_x11_egl_windowinfo_create,
  526. .windowinfo_destroy = gl_x11_egl_windowinfo_destroy,
  527. .platform_create = gl_x11_egl_platform_create,
  528. .platform_destroy = gl_x11_egl_platform_destroy,
  529. .platform_init_swapchain = gl_x11_egl_platform_init_swapchain,
  530. .platform_cleanup_swapchain = gl_x11_egl_platform_cleanup_swapchain,
  531. .device_enter_context = gl_x11_egl_device_enter_context,
  532. .device_leave_context = gl_x11_egl_device_leave_context,
  533. .device_get_device_obj = gl_x11_egl_device_get_device_obj,
  534. .getclientsize = gl_x11_egl_getclientsize,
  535. .clear_context = gl_x11_egl_clear_context,
  536. .update = gl_x11_egl_update,
  537. .device_load_swapchain = gl_x11_egl_device_load_swapchain,
  538. .device_present = gl_x11_egl_device_present,
  539. };
  540. const struct gl_winsys_vtable *gl_x11_egl_get_winsys_vtable(void)
  541. {
  542. return &egl_x11_winsys_vtable;
  543. }