pax_global_header00006660000000000000000000000064147174312620014521gustar00rootroot0000000000000052 comment=c616565cb830a23ac69ddd3c78251711646a11a2 egl-x11-1.0.0/000077500000000000000000000000001471743126200126755ustar00rootroot00000000000000egl-x11-1.0.0/LICENSE000066400000000000000000000261361471743126200137120ustar00rootroot00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. egl-x11-1.0.0/README.md000066400000000000000000000017611471743126200141610ustar00rootroot00000000000000NVIDIA XLib and XCB EGL Platform Library ================================ Overview -------- This is an EGL platform library for the NVIDIA driver to support XWayland via xlib (using `EGL_KHR_platform_x11`) or xcb (using `EGL_EXT_platform_xcb`). Building and Installing ----------------------- This library depends on: - libxcb, libxcb-present, and libxcb-dri3, version 1.17.0 - libgbm, version 21.3.0 - libdrm, version 2.4.99 - libx11 (only if building the xlib library) - EGL headers In addition, this library depends on a (still somewhat experimental) interface in the NVIDIA driver, which is supported only in 560 or later series drivers. For full functionality, it also needs the explicit sync protocol added to version 1.4 of the Present and DRI3 extensions, which is available in XWayland 24.1 and later. Without explicit sync support, you may get reduced performance and out-of-order frames. To build and install, use Meson: ```sh meson builddir ninja -C builddir ninja -C builddir install ``` egl-x11-1.0.0/meson.build000066400000000000000000000021651471743126200150430ustar00rootroot00000000000000# SPDX-FileCopyrightText: Copyright (c) 2024 NVIDIA CORPORATION & AFFILIATES. All rights reserved. # SPDX-License-Identifier: Apache-2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. project('egl-x11', 'c', version : '1.0.0', default_options : ['c_std=gnu99'], ) dep_libdrm = dependency('libdrm') dep_threads = dependency('threads') dep_eglexternal = dependency('eglexternalplatform', version : ['>=1.2', '<2']) inc_base = include_directories('src/base') cc = meson.get_compiler('c') if cc.compiles('typeof(int *);', name : 'typeof') add_project_arguments('-DHAVE_TYPEOF', language : ['c']) endif subdir('src/base') subdir('src/x11') egl-x11-1.0.0/meson_options.txt000066400000000000000000000003171471743126200163330ustar00rootroot00000000000000option( 'xlib', type : 'feature', description : 'Build a platform library for EGL_PLATFORM_X11' ) option( 'xcb', type : 'boolean', description : 'Build a platform library for EGL_PLATFORM_XCB' ) egl-x11-1.0.0/src/000077500000000000000000000000001471743126200134645ustar00rootroot00000000000000egl-x11-1.0.0/src/base/000077500000000000000000000000001471743126200143765ustar00rootroot00000000000000egl-x11-1.0.0/src/base/config-list.c000066400000000000000000000311541471743126200167640ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: Copyright (c) 2024 NVIDIA CORPORATION & AFFILIATES. All rights reserved. * SPDX-License-Identifier: Apache-2.0 * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "config-list.h" #include #include #include #include #include #include "platform-utils.h" const EplFormatInfo FORMAT_INFO_LIST[] = { { DRM_FORMAT_ARGB8888, 32, { 8, 8, 8, 8 }, { 16, 8, 0, 24 } }, { DRM_FORMAT_RGBA8888, 32, { 8, 8, 8, 8 }, { 24, 16, 8, 0 } }, { DRM_FORMAT_XRGB8888, 32, { 8, 8, 8, 0 }, { 16, 8, 0, 0 } }, { DRM_FORMAT_RGB888, 24, { 8, 8, 8, 0 }, { 16, 8, 0, 0 } }, { DRM_FORMAT_XBGR8888, 32, { 8, 8, 8, 0 }, { 0, 8, 16, 0 } }, { DRM_FORMAT_ABGR8888, 32, { 8, 8, 8, 8 }, { 0, 8, 16, 24 } }, { DRM_FORMAT_R8, 8, { 8, 0, 0, 0 }, { 0, 0, 0, 0 } }, { DRM_FORMAT_RG88, 16, { 8, 8, 0, 0 }, { 8, 0, 0, 0 } }, { DRM_FORMAT_R16, 16, { 16, 0, 0, 0 }, { 0, 0, 0, 0 } }, { DRM_FORMAT_RG1616, 32, { 16, 16, 0, 0 }, { 16, 0, 0, 0 } }, { DRM_FORMAT_ARGB2101010, 32, { 10, 10, 10, 2 }, { 20, 10, 0, 30 } }, { DRM_FORMAT_ABGR2101010, 32, { 10, 10, 10, 2 }, { 0, 10, 20, 30 } }, /* 8 bpp RGB */ { DRM_FORMAT_RGB332, 8, { 3, 3, 2, 0 }, { 5, 2, 0, 0 } }, /* 16 bpp RGB */ { DRM_FORMAT_ARGB4444, 16, { 4, 4, 4, 4 }, { 8, 4, 0, 12 } }, { DRM_FORMAT_ABGR4444, 16, { 4, 4, 4, 4 }, { 0, 4, 8, 12 } }, { DRM_FORMAT_RGBA4444, 16, { 4, 4, 4, 4 }, { 12, 8, 4, 0 } }, { DRM_FORMAT_BGRA4444, 16, { 4, 4, 4, 4 }, { 4, 8, 12, 0 } }, { DRM_FORMAT_XRGB4444, 16, { 4, 4, 4, 0 }, { 8, 4, 0, 0 } }, { DRM_FORMAT_XBGR4444, 16, { 4, 4, 4, 0 }, { 0, 4, 8, 0 } }, { DRM_FORMAT_RGBX4444, 16, { 4, 4, 4, 0 }, { 12, 8, 4, 0 } }, { DRM_FORMAT_BGRX4444, 16, { 4, 4, 4, 0 }, { 4, 8, 12, 0 } }, { DRM_FORMAT_XRGB1555, 16, { 5, 5, 5, 0 }, { 10, 5, 0, 0 } }, { DRM_FORMAT_XBGR1555, 16, { 5, 5, 5, 0 }, { 0, 5, 10, 0 } }, { DRM_FORMAT_RGBX5551, 16, { 5, 5, 5, 0 }, { 11, 6, 1, 0 } }, { DRM_FORMAT_BGRX5551, 16, { 5, 5, 5, 0 }, { 1, 6, 11, 0 } }, { DRM_FORMAT_ARGB1555, 16, { 5, 5, 5, 1 }, { 10, 5, 0, 15 } }, { DRM_FORMAT_ABGR1555, 16, { 5, 5, 5, 1 }, { 0, 5, 10, 15 } }, { DRM_FORMAT_RGBA5551, 16, { 5, 5, 5, 1 }, { 11, 6, 1, 0 } }, { DRM_FORMAT_BGRA5551, 16, { 5, 5, 5, 1 }, { 1, 6, 11, 0 } }, { DRM_FORMAT_RGB565, 16, { 5, 6, 5, 0 }, { 11, 5, 0, 0 } }, { DRM_FORMAT_BGR565, 16, { 5, 6, 5, 0 }, { 0, 5, 11, 0 } }, /* 24 bpp RGB */ //{ DRM_FORMAT_RGB888, 24, { 8, 8, 8, 0 }, { 16, 8, 0, 0 } }, { DRM_FORMAT_BGR888, 24, { 8, 8, 8, 0 }, { 0, 8, 16, 0 } }, /* 32 bpp RGB */ { DRM_FORMAT_RGBX8888, 32, { 8, 8, 8, 0 }, { 24, 16, 8, 0 } }, { DRM_FORMAT_BGRX8888, 32, { 8, 8, 8, 0 }, { 8, 16, 24, 0 } }, //{ DRM_FORMAT_RGBA8888, 32, { 8, 8, 8, 8 }, { 24, 16, 8, 0 } }, { DRM_FORMAT_BGRA8888, 32, { 8, 8, 8, 8 }, { 8, 16, 24, 0 } }, { DRM_FORMAT_XRGB2101010, 32, { 10, 10, 10, 0 }, { 20, 10, 0, 0 } }, { DRM_FORMAT_XBGR2101010, 32, { 10, 10, 10, 0 }, { 0, 10, 20, 0 } }, { DRM_FORMAT_RGBX1010102, 32, { 10, 10, 10, 0 }, { 22, 12, 2, 0 } }, { DRM_FORMAT_BGRX1010102, 32, { 10, 10, 10, 0 }, { 2, 12, 22, 0 } }, { DRM_FORMAT_RGBA1010102, 32, { 10, 10, 10, 2 }, { 22, 12, 2, 0 } }, { DRM_FORMAT_BGRA1010102, 32, { 10, 10, 10, 2 }, { 2, 12, 22, 0 } }, { DRM_FORMAT_INVALID } }; const int FORMAT_INFO_COUNT = (sizeof(FORMAT_INFO_LIST) / sizeof(FORMAT_INFO_LIST[0])) - 1; // Note: Since the EGLConfig is the first element of EplConfig, this function // should work for sorting and searching an array of EGLConfig or EplConfig. static int CompareConfig(const void *p1, const void *p2) { uintptr_t v1 = *((const uintptr_t *) p1); uintptr_t v2 = *((const uintptr_t *) p2); if (v1 < v2) { return -1; } else if (v1 > v2) { return 1; } else { return 0; } } static void LookupConfigInfo(EplPlatformData *platform, EGLDisplay edpy, EGLConfig config, EplConfig *info) { EGLint color[4] = { 0, 0, 0, 0 }; EGLint surfaceMask = 0; EGLint i; memset(info, 0, sizeof(*info)); info->config = config; info->nativeVisualID = 0; info->nativeVisualType = EGL_NONE; if (!platform->egl.GetConfigAttrib(edpy, config, EGL_RED_SIZE, &color[0]) || !platform->egl.GetConfigAttrib(edpy, config, EGL_GREEN_SIZE, &color[1]) || !platform->egl.GetConfigAttrib(edpy, config, EGL_BLUE_SIZE, &color[2]) || !platform->egl.GetConfigAttrib(edpy, config, EGL_ALPHA_SIZE, &color[3]) || !platform->egl.GetConfigAttrib(edpy, config, EGL_SURFACE_TYPE, &surfaceMask)) { return; } info->surfaceMask = surfaceMask; // For now, just find a format with the right color sizes. info->fourcc = DRM_FORMAT_INVALID; for (i=0; ifourcc = FORMAT_INFO_LIST[i].fourcc; break; } } } EplConfigList *eplConfigListCreate(EplPlatformData *platform, EGLDisplay edpy) { EplConfigList *list = NULL; EGLConfig *driverConfigs = NULL; EGLint numConfigs = 0; EGLint i; if (!platform->egl.GetConfigs(edpy, NULL, 0, &numConfigs) || numConfigs <= 0) { return NULL; } driverConfigs = malloc(numConfigs * sizeof(EGLConfig)); if (driverConfigs == NULL) { eplSetError(platform, EGL_BAD_ALLOC, "Out of memory"); return NULL; } if (!platform->egl.GetConfigs(edpy, driverConfigs, numConfigs, &numConfigs) || numConfigs <= 0) { free(driverConfigs); return NULL; } qsort(driverConfigs, numConfigs, sizeof(EGLConfig), CompareConfig); list = malloc(sizeof(EplConfigList) + numConfigs * sizeof(EplConfig)); if (list == NULL) { eplSetError(platform, EGL_BAD_ALLOC, "Out of memory"); free(driverConfigs); return NULL; } list->configs = (EplConfig *) (list + 1); list->num_configs = numConfigs; for (i=0; iconfigs[i]); } free(driverConfigs); return list; } void eplConfigListFree(EplConfigList *list) { free(list); } EplConfig *eplConfigListFind(EplConfigList *list, EGLConfig config) { EplConfig *found = bsearch(&config, list->configs, list->num_configs, sizeof(EplConfig), CompareConfig); return found; } EGLint eplConfigListFindIndex(EplConfigList *list, EGLConfig config) { EplConfig *found = eplConfigListFind(list, config); if (found != NULL) { return (EGLint) (found - list->configs); } else { return -1; } } EplConfig **eplConfigListChooseConfigs(EplPlatformData *platform, EGLDisplay edpy, EplConfigList *list, const EGLint *attribs, EGLint *ret_count, EGLint *ret_native_pixmap) { EGLint surfaceMask = EGL_WINDOW_BIT; EGLint nativeRenderable = EGL_DONT_CARE; EGLint nativeVisualType = EGL_DONT_CARE; EGLint numAttribs = eplCountAttribs32(attribs); EGLint *attribsCopy = NULL; EGLConfig *internalConfigs = NULL; EGLint internalCount = 0; EplConfig **configs = NULL; EGLBoolean success = EGL_FALSE; EGLint matchCount = 0; EGLint i; attribsCopy = malloc((numAttribs + 3) * sizeof(EGLint)); if (attribsCopy == NULL) { eplSetError(platform, EGL_BAD_ALLOC, "Out of memory"); goto done; } // Copy and filter out any attributes that we need to special case. numAttribs = 0; if (attribs != NULL) { for (i=0; attribs[i] != EGL_NONE; i += 2) { if (attribs[i] == EGL_MATCH_NATIVE_PIXMAP) { if (ret_native_pixmap != NULL) { *ret_native_pixmap = attribs[i + 1]; } } else if (attribs[i] == EGL_SURFACE_TYPE) { surfaceMask = attribs[i + 1]; } else if (attribs[i] == EGL_NATIVE_RENDERABLE) { nativeRenderable = attribs[i + 1]; } else if (attribs[i] == EGL_NATIVE_VISUAL_TYPE) { nativeVisualType = attribs[i + 1]; } else { attribsCopy[numAttribs++] = attribs[i]; attribsCopy[numAttribs++] = attribs[i + 1]; } } } // Get configs for all surface types. We'll filter those manually below. attribsCopy[numAttribs++] = EGL_SURFACE_TYPE; attribsCopy[numAttribs++] = EGL_DONT_CARE; attribsCopy[numAttribs] = EGL_NONE; if (!platform->egl.ChooseConfig(edpy, attribsCopy, NULL, 0, &internalCount) || internalCount <= 0) { goto done; } internalConfigs = malloc(internalCount * sizeof(EGLConfig)); configs = malloc((internalCount + 1) * sizeof(EplConfig *)); if (internalConfigs == NULL || configs == NULL) { eplSetError(platform, EGL_BAD_ALLOC, "Out of memory"); goto done; } if (!platform->egl.ChooseConfig(edpy, attribsCopy, internalConfigs, internalCount, &internalCount) || internalCount <= 0) { goto done; } matchCount = 0; for (i=0; isurfaceMask & surfaceMask) != surfaceMask) { continue; } } if (nativeRenderable != EGL_DONT_CARE) { if (info->nativeRenderable != (nativeRenderable != 0)) { continue; } } if (nativeVisualType != EGL_DONT_CARE) { if (info->nativeVisualType != nativeVisualType) { continue; } } configs[matchCount++] = info; } configs[matchCount] = NULL; success = EGL_TRUE; done: if (success) { if (ret_count != NULL) { *ret_count = matchCount; } } else { free(configs); configs = NULL; } free(attribsCopy); free(internalConfigs); return configs; } void eplConfigListReturnConfigs(EplConfig **configs, EGLint count, EGLConfig *ret_configs, EGLint max, EGLint *ret_count) { EGLint num = count; if (ret_configs != NULL) { EGLint i; if (num > max) { num = max; } for (i=0; iconfig; } } if (ret_count != NULL) { *ret_count = num; } } EGLBoolean eplConfigListGetAttribute(EplPlatformData *platform, EGLDisplay edpy, EplConfigList *list, EGLConfig config, EGLint attribute, EGLint *value) { const EplConfig *info; EGLBoolean success = EGL_TRUE; EGLint val = 0; info = eplConfigListFind(list, config); if (info == NULL) { eplSetError(platform, EGL_BAD_CONFIG, "Invalid EGLConfig %p", config); return EGL_FALSE; } if (attribute == EGL_SURFACE_TYPE) { val = info->surfaceMask; } else if (attribute == EGL_NATIVE_VISUAL_ID) { val = info->nativeVisualID; } else if (attribute == EGL_NATIVE_VISUAL_TYPE) { val = info->nativeVisualType; } else if (attribute == EGL_NATIVE_RENDERABLE) { val = info->nativeRenderable; } else { success = platform->egl.GetConfigAttrib(edpy, config, attribute, &val); } if (success && value != NULL) { *value = val; } return success; } const EplFormatInfo *eplFormatInfoLookup(uint32_t fourcc) { int i; for (i=0; i #include #include #include "platform-base.h" #ifdef __cplusplus extern "C" { #endif /** * Contains some basic information about a fourcc format. * * This is used to match a format to things like an X11 visual. */ typedef struct { uint32_t fourcc; int bpp; int colors[4]; int offset[4]; } EplFormatInfo; /** * Contains information about an EGLConfig. */ typedef struct { /** * The EGLConfig handle. * * Note that this must be the first element in EplConfig. */ EGLConfig config; /** * The fourcc format code. Currently set based on the color sizes. */ uint32_t fourcc; /** * The value of EGL_SURFACE_TYPE for this config. * * This is initially set to whatever the driver hands back. The platform * library can then add EGL_WINDOW_BIT and EGL_PIXMAP_BIT as appropriate. */ EGLint surfaceMask; /** * The value of EGL_NATIVE_VISUAL_ID. Initially set to zero. */ EGLint nativeVisualID; /** * The value of EGL_NATIVE_VISUAL_TYPE. * * Initially set to EGL_NONE. */ EGLint nativeVisualType; /** * The value of EGL_NATIVE_RENDERABLE. * * Initially set to EGL_FALSE. */ EGLBoolean nativeRenderable; } EplConfig; /** * An array of known color formats. * * Note that this list is *not* sorted by fourcc value. It's sorted based on * which formats to pick if we don't know/care what the color order is. */ extern const EplFormatInfo FORMAT_INFO_LIST[]; extern const int FORMAT_INFO_COUNT; typedef struct { /** * A list of EplConfigs, sorted by the EGLConfig handle. */ EplConfig *configs; EGLint num_configs; } EplConfigList; /** * Looks up all available EGLConfigs. * * \param edpy The internal EGLDisplay. * \return A new EplConfigList struct. */ EplConfigList *eplConfigListCreate(EplPlatformData *platform, EGLDisplay edpy); void eplConfigListFree(EplConfigList *list); /** * Looks up an EplConfig by its EGLConfig handle. * * \param list The EplConfigList to search. * \param config The EGLConfig handle to search for. * \return The matching EplConfig struct, or NULL if \p config was not found. */ EplConfig *eplConfigListFind(EplConfigList *list, EGLConfig config); /** * A helper function for handling eglChooseConfig. * * This will fetch a list of EGLConfigs from the driver, and then filter that * list based on the EGL_SURFACE_TYPE, EGL_NATIVE_VISUAL_TYPE, and * EGL_NATIVE_RENDERABLE attributes. * * This does not filter based on EGL_MATCH_NATIVE_PIXMAP, since that requires * platform-specific code. Instead, if EGL_MATCH_NATIVE_PIXMAP is included in * \p attribs, then the value is returned in \p ret_native_pixmap. Otherwise, * \p ret_native_pixmap is left unchanged. * * This function returns a NULL-terminated array of EplConfig pointers, so the * caller can do any additional filtering as needed. You can use * eplConfigListReturnConfigs to copy the results to an EGLConfig array. * * \param platform The platform data. * \param edpy The internal EGLDisplay. * \param list The list to search. * \param attribs The attribute list, as provided by the application. * \param[out] ret_count Optionally returns the number of matching attributes, * not including the NULL terminator. * \param[out] ret_native_pixmap Optionally returns the value of the * EGL_MATCH_NATIVE_PIXMAP attribute. If that attribtue isn't in he list, * then this is left unchanged. * \return A NULL-termianted array of EplConfigs, or NULL on error. */ EplConfig **eplConfigListChooseConfigs(EplPlatformData *platform, EGLDisplay edpy, EplConfigList *list, const EGLint *attribs, EGLint *ret_count, EGLint *ret_native_pixmap); /** * Copies the EGLConfig handles from an EplConfig array to an EGLConfig array. * * This is used in conjunction with eplConfigListChooseConfigs to handle * eglChooseConfig. */ void eplConfigListReturnConfigs(EplConfig **configs, EGLint count, EGLConfig *ret_configs, EGLint max, EGLint *ret_count); /** * A helper function for handling eglGetConfigAttrib. * * This function will fill in results for attributes that are stored in the * EplConfig struct. * * Currently, that includes EGL_SURFACE_TYPE, EGL_NATIVE_VISUAL_ID, and * EGL_NATIVE_VISUAL_TYPE, and EGL_NATIVE_RENDERABLE. * * For anything else, it will call through to the driver. */ EGLBoolean eplConfigListGetAttribute(EplPlatformData *platform, EGLDisplay edpy, EplConfigList *list, EGLConfig config, EGLint attribute, EGLint *value); /** * Returns the index of an EplConfig. * * \param list The EplConfigList to search. * \param config The EGLConfig handle to search for. * \return The index of the matching EplConfig struct, or -1 if \p config was * not found. */ EGLint eplConfigListFindIndex(EplConfigList *list, EGLConfig config); const EplFormatInfo *eplFormatInfoLookup(uint32_t fourcc); static inline int eplFormatInfoDepth(const EplFormatInfo *fmt) { return fmt->colors[0] + fmt->colors[1] + fmt->colors[2] + fmt->colors[3]; } #ifdef __cplusplus } #endif #endif // CONFIG_LIST_H egl-x11-1.0.0/src/base/glvnd_list.h000066400000000000000000000352771471743126200167320ustar00rootroot00000000000000/* * Copyright © 2010 Intel Corporation * Copyright © 2010 Francisco Jerez * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice (including the next * paragraph) shall be included in all copies or substantial portions of the * Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS * IN THE SOFTWARE. * */ #ifndef _GLVND_LIST_H_ #define _GLVND_LIST_H_ #include /* offsetof() */ /** * @file Classic doubly-link circular list implementation. * For real usage examples of the linked list, see the file test/list.c * * Example: * We need to keep a list of struct foo in the parent struct bar, i.e. what * we want is something like this. * * struct bar { * ... * struct foo *list_of_foos; -----> struct foo {}, struct foo {}, struct foo{} * ... * } * * We need one list head in bar and a list element in all list_of_foos (both are of * data type 'struct glvnd_list'). * * struct bar { * ... * struct glvnd_list list_of_foos; * ... * } * * struct foo { * ... * struct glvnd_list entry; * ... * } * * Now we initialize the list head: * * struct bar bar; * ... * glvnd_list_init(&bar.list_of_foos); * * Then we create the first element and add it to this list: * * struct foo *foo = malloc(...); * .... * glvnd_list_add(&foo->entry, &bar.list_of_foos); * * Repeat the above for each element you want to add to the list. Deleting * works with the element itself. * glvnd_list_del(&foo->entry); * free(foo); * * Note: calling glvnd_list_del(&bar.list_of_foos) will set bar.list_of_foos to an empty * list again. * * Looping through the list requires a 'struct foo' as iterator and the * name of the field the subnodes use. * * struct foo *iterator; * glvnd_list_for_each_entry(iterator, &bar.list_of_foos, entry) { * if (iterator->something == ...) * ... * } * * Note: You must not call glvnd_list_del() on the iterator if you continue the * loop. You need to run the safe for-each loop instead: * * struct foo *iterator, *next; * glvnd_list_for_each_entry_safe(iterator, next, &bar.list_of_foos, entry) { * if (...) * glvnd_list_del(&iterator->entry); * } * */ /** * The linkage struct for list nodes. This struct must be part of your * to-be-linked struct. struct glvnd_list is required for both the head of the * list and for each list node. * * Position and name of the struct glvnd_list field is irrelevant. * There are no requirements that elements of a list are of the same type. * There are no requirements for a list head, any struct glvnd_list can be a list * head. */ struct glvnd_list { struct glvnd_list *next, *prev; }; /** * Initialize the list as an empty list. * * Example: * glvnd_list_init(&bar->list_of_foos); * * @param The list to initialized. */ static inline void glvnd_list_init(struct glvnd_list *list) { list->next = list->prev = list; } static inline void __glvnd_list_add(struct glvnd_list *entry, struct glvnd_list *prev, struct glvnd_list *next) { next->prev = entry; entry->next = next; entry->prev = prev; prev->next = entry; } /** * Insert a new element after the given list head. The new element does not * need to be initialised as empty list. * The list changes from: * head → some element → ... * to * head → new element → older element → ... * * Example: * struct foo *newfoo = malloc(...); * glvnd_list_add(&newfoo->entry, &bar->list_of_foos); * * @param entry The new element to prepend to the list. * @param head The existing list. */ static inline void glvnd_list_add(struct glvnd_list *entry, struct glvnd_list *head) { __glvnd_list_add(entry, head, head->next); } /** * Append a new element to the end of the list given with this list head. * * The list changes from: * head → some element → ... → lastelement * to * head → some element → ... → lastelement → new element * * Example: * struct foo *newfoo = malloc(...); * glvnd_list_append(&newfoo->entry, &bar->list_of_foos); * * @param entry The new element to prepend to the list. * @param head The existing list. */ static inline void glvnd_list_append(struct glvnd_list *entry, struct glvnd_list *head) { __glvnd_list_add(entry, head->prev, head); } static inline void __glvnd_list_del(struct glvnd_list *prev, struct glvnd_list *next) { next->prev = prev; prev->next = next; } /** * Remove the element from the list it is in. Using this function will reset * the pointers to/from this element so it is removed from the list. It does * NOT free the element itself or manipulate it otherwise. * * Using glvnd_list_del on a pure list head (like in the example at the top of * this file) will NOT remove the first element from * the list but rather reset the list as empty list. * * Example: * glvnd_list_del(&foo->entry); * * @param entry The element to remove. */ static inline void glvnd_list_del(struct glvnd_list *entry) { __glvnd_list_del(entry->prev, entry->next); glvnd_list_init(entry); } /** * Check if the list is empty. * * Example: * glvnd_list_is_empty(&bar->list_of_foos); * * @return True if the list contains one or more elements or False otherwise. */ static inline int glvnd_list_is_empty(struct glvnd_list *head) { return head->next == head; } /** * Returns a pointer to the container of this list element. * * Example: * struct foo* f; * f = glvnd_container_of(&foo->entry, struct foo, entry); * assert(f == foo); * * @param ptr Pointer to the struct glvnd_list. * @param type Data type of the list element. * @param member Member name of the struct glvnd_list field in the list element. * @return A pointer to the data struct containing the list head. */ #ifndef glvnd_container_of #define glvnd_container_of(ptr, type, member) \ (type *)((char *)(ptr) - offsetof(type, member)) #endif /** * Alias of glvnd_container_of */ #define glvnd_list_entry(ptr, type, member) \ glvnd_container_of(ptr, type, member) /** * Retrieve the first list entry for the given list pointer. * * Example: * struct foo *first; * first = glvnd_list_first_entry(&bar->list_of_foos, struct foo, list_of_foos); * * @param ptr The list head * @param type Data type of the list element to retrieve * @param member Member name of the struct glvnd_list field in the list element. * @return A pointer to the first list element. */ #define glvnd_list_first_entry(ptr, type, member) \ glvnd_list_entry((ptr)->next, type, member) /** * Retrieve the last list entry for the given listpointer. * * Example: * struct foo *first; * first = glvnd_list_last_entry(&bar->list_of_foos, struct foo, list_of_foos); * * @param ptr The list head * @param type Data type of the list element to retrieve * @param member Member name of the struct glvnd_list field in the list element. * @return A pointer to the last list element. */ #define glvnd_list_last_entry(ptr, type, member) \ glvnd_list_entry((ptr)->prev, type, member) #ifdef HAVE_TYPEOF #define __glvnd_container_of(ptr, sample, member) \ glvnd_container_of(ptr, typeof(*sample), member) #else /* This implementation of __glvnd_container_of has undefined behavior according * to the C standard, but it works in many cases. If your compiler doesn't * support typeof() and fails with this implementation, please try a newer * compiler. */ #warning "typeof() is not supported. The fallback for this is undefined behavior." #define __glvnd_container_of(ptr, sample, member) \ (void *)((char *)(ptr) \ - ((char *)&(sample)->member - (char *)(sample))) #endif /** * Loop through the list given by head and set pos to struct in the list. * * Example: * struct foo *iterator; * glvnd_list_for_each_entry(iterator, &bar->list_of_foos, entry) { * [modify iterator] * } * * This macro is not safe for node deletion. Use glvnd_list_for_each_entry_safe * instead. * * @param pos Iterator variable of the type of the list elements. * @param head List head * @param member Member name of the struct glvnd_list in the list elements. * */ #define glvnd_list_for_each_entry(pos, head, member) \ for (pos = __glvnd_container_of((head)->next, pos, member); \ &pos->member != (head); \ pos = __glvnd_container_of(pos->member.next, pos, member)) /** * Loop through the list, keeping a backup pointer to the element. This * macro allows for the deletion of a list element while looping through the * list. * * See glvnd_list_for_each_entry for more details. */ #define glvnd_list_for_each_entry_safe(pos, tmp, head, member) \ for (pos = __glvnd_container_of((head)->next, pos, member), \ tmp = __glvnd_container_of(pos->member.next, pos, member); \ &pos->member != (head); \ pos = tmp, tmp = __glvnd_container_of(pos->member.next, tmp, member)) /* NULL-Terminated List Interface * * The interface below does _not_ use the struct glvnd_list as described above. * It is mainly for legacy structures that cannot easily be switched to * struct glvnd_list. * * This interface is for structs like * struct foo { * [...] * struct foo *next; * [...] * }; * * The position and field name of "next" are arbitrary. */ /** * Init the element as null-terminated list. * * Example: * struct foo *list = malloc(); * glvnd_nt_list_init(list, next); * * @param list The list element that will be the start of the list * @param member Member name of the field pointing to next struct */ #define glvnd_nt_list_init(_list, _member) \ (_list)->_member = NULL /** * Returns the next element in the list or NULL on termination. * * Example: * struct foo *element = list; * while ((element = glvnd_nt_list_next(element, next)) { } * * This macro is not safe for node deletion. Use glvnd_nt_list_for_each_entry_safe * instead. * * @param list The list or current element. * @param member Member name of the field pointing to next struct. */ #define glvnd_nt_list_next(_list, _member) \ (_list)->_member /** * Iterate through each element in the list. * * Example: * struct foo *iterator; * glvnd_nt_list_for_each_entry(iterator, list, next) { * [modify iterator] * } * * @param entry Assigned to the current list element * @param list The list to iterate through. * @param member Member name of the field pointing to next struct. */ #define glvnd_nt_list_for_each_entry(_entry, _list, _member) \ for (_entry = _list; _entry; _entry = (_entry)->_member) /** * Iterate through each element in the list, keeping a backup pointer to the * element. This macro allows for the deletion of a list element while * looping through the list. * * See glvnd_nt_list_for_each_entry for more details. * * @param entry Assigned to the current list element * @param tmp The pointer to the next element * @param list The list to iterate through. * @param member Member name of the field pointing to next struct. */ #define glvnd_nt_list_for_each_entry_safe(_entry, _tmp, _list, _member) \ for (_entry = _list, _tmp = (_entry) ? (_entry)->_member : NULL;\ _entry; \ _entry = _tmp, _tmp = (_tmp) ? (_tmp)->_member: NULL) /** * Append the element to the end of the list. This macro may be used to * merge two lists. * * Example: * struct foo *elem = malloc(...); * glvnd_nt_list_init(elem, next) * glvnd_nt_list_append(elem, list, struct foo, next); * * Resulting list order: * list_item_0 -> list_item_1 -> ... -> elem_item_0 -> elem_item_1 ... * * @param entry An entry (or list) to append to the list * @param list The list to append to. This list must be a valid list, not * NULL. * @param type The list type * @param member Member name of the field pointing to next struct */ #define glvnd_nt_list_append(_entry, _list, _type, _member) \ do { \ _type *__iterator = _list; \ while (__iterator->_member) { __iterator = __iterator->_member;}\ __iterator->_member = _entry; \ } while (0) /** * Insert the element at the next position in the list. This macro may be * used to insert a list into a list. * * struct foo *elem = malloc(...); * glvnd_nt_list_init(elem, next) * glvnd_nt_list_insert(elem, list, struct foo, next); * * Resulting list order: * list_item_0 -> elem_item_0 -> elem_item_1 ... -> list_item_1 -> ... * * @param entry An entry (or list) to append to the list * @param list The list to insert to. This list must be a valid list, not * NULL. * @param type The list type * @param member Member name of the field pointing to next struct */ #define glvnd_nt_list_insert(_entry, _list, _type, _member) \ do { \ glvnd_nt_list_append((_list)->_member, _entry, _type, _member); \ (_list)->_member = _entry; \ } while (0) /** * Delete the entry from the list by iterating through the list and * removing any reference from the list to the entry. * * Example: * struct foo *elem = * glvnd_nt_list_del(elem, list, struct foo, next); * * @param entry The entry to delete from the list. entry is always * re-initialized as a null-terminated list. * @param list The list containing the entry, set to the new list without * the removed entry. * @param type The list type * @param member Member name of the field pointing to the next entry */ #define glvnd_nt_list_del(_entry, _list, _type, _member) \ do { \ _type *__e = _entry; \ if (__e == NULL || _list == NULL) break; \ if ((_list) == __e) { \ _list = __e->_member; \ } else { \ _type *__prev = _list; \ while (__prev->_member && __prev->_member != __e) \ __prev = glvnd_nt_list_next(__prev, _member); \ if (__prev->_member) \ __prev->_member = __e->_member; \ } \ glvnd_nt_list_init(__e, _member); \ } while(0) #endif egl-x11-1.0.0/src/base/meson.build000066400000000000000000000017651471743126200165510ustar00rootroot00000000000000# SPDX-FileCopyrightText: Copyright (c) 2024 NVIDIA CORPORATION & AFFILIATES. All rights reserved. # SPDX-License-Identifier: Apache-2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. platform_base = static_library('platform-base', [ 'config-list.c', 'platform-base.c', 'platform-utils.c', 'refcountobj.c', ], dependencies: [ dep_libdrm.partial_dependency(compile_args : true, includes : true), dep_threads, dep_eglexternal, ], gnu_symbol_visibility: 'hidden', install: false) egl-x11-1.0.0/src/base/platform-base.c000066400000000000000000001157171471743126200173120ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: Copyright (c) 2024 NVIDIA CORPORATION & AFFILIATES. All rights reserved. * SPDX-License-Identifier: Apache-2.0 * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "platform-base.h" #include #include #include #include #include #include "platform-utils.h" #include "platform-impl.h" #define USE_ERRORCHECK_MUTEX 1 static void *eplGetHookAddressExport(void *platformData, const char *name); static EGLBoolean eplIsValidNativeDisplayExport(void *platformData, void *nativeDisplay); static EGLDisplay eplGetPlatformDisplayExport(void *platformData, EGLenum platform, void *nativeDisplay, const EGLAttrib* attribs); static const char *eplQueryStringExport(void *platformData, EGLDisplay edpy, EGLExtPlatformString name); static void *eplGetInternalHandleExport(EGLDisplay edpy, EGLenum type, void *handle); static EGLBoolean eplUnloadExternalPlatformExport(void *platformData); static void DeleteSurfaceCommon(EplDisplay *pdpy, EplSurface *psurf); static EplSurface *AllocBaseSurface(EplPlatformData *plat); static void FreeBaseSurface(EplSurface *psurf); static void TerminateDisplay(EplDisplay *pdpy); static void CheckTerminateDisplay(EplDisplay *pdpy); static void DestroyDisplay(EplDisplay *pdpy); /** * A list of all EplDisplay structs. */ static struct glvnd_list display_list = { &display_list, &display_list }; static pthread_mutex_t display_list_mutex; /** * A list of all EplPlatformData structs. * * This is here for cleanup handling. */ static struct glvnd_list platform_data_list = { &platform_data_list, &platform_data_list }; static pthread_mutex_t platform_data_list_mutex = PTHREAD_MUTEX_INITIALIZER; static __attribute__((constructor)) void LibraryInit(void) { eplInitRecursiveMutex(&display_list_mutex); } static __attribute__((destructor)) void LibraryFini(void) { pthread_mutex_destroy(&display_list_mutex); } EPL_REFCOUNT_DEFINE_TYPE_FUNCS(EplPlatformData, eplPlatformData, refcount, free); EPL_REFCOUNT_DEFINE_TYPE_FUNCS(EplInternalDisplay, eplInternalDisplay, refcount, free); EplPlatformData *eplPlatformBaseAllocate(int major, int minor, const EGLExtDriver *driver, EGLExtPlatform *extplatform, EGLenum platform_enum, const EplImplFuncs *impl, size_t platform_priv_size) { EplPlatformData *platform = NULL; const char *str; // Assert that all of the required implementation functions are provided. assert(impl->QueryString != NULL); assert(impl->GetPlatformDisplay != NULL); assert(impl->CleanupDisplay != NULL); assert(impl->InitializeDisplay != NULL); assert(impl->TerminateDisplay != NULL); assert(impl->DestroySurface != NULL); assert(impl->FreeSurface != NULL); // SwapBuffers is only required if the platform supports windows. assert(impl->CreateWindowSurface == NULL || impl->SwapBuffers != NULL); // HACK: EGL_EXTERNAL_PLATFORM_VERSION_CHECK is backwards and doesn't work. if (extplatform == NULL || !EGL_EXTERNAL_PLATFORM_VERSION_CMP(major, minor, EGL_EXTERNAL_PLATFORM_VERSION_MAJOR, EGL_EXTERNAL_PLATFORM_VERSION_MINOR)) { return NULL; } platform = calloc(1, sizeof(EplPlatformData) + platform_priv_size); if (platform == NULL) { return NULL; } eplRefCountInit(&platform->refcount); platform->impl = impl; platform->platform_enum = platform_enum; if (platform_priv_size > 0) { platform->priv = (EplImplPlatform *) (platform + 1); } glvnd_list_init(&platform->entry); glvnd_list_init(&platform->internal_display_list); platform->callbacks.getProcAddress = driver->getProcAddress; platform->callbacks.setError = driver->setError; platform->callbacks.debugMessage = driver->debugMessage; platform->egl.QueryString = driver->getProcAddress("eglQueryString"); platform->egl.GetPlatformDisplay = driver->getProcAddress("eglGetPlatformDisplay"); platform->egl.Initialize = driver->getProcAddress("eglInitialize"); platform->egl.Terminate = driver->getProcAddress("eglTerminate"); platform->egl.GetError = driver->getProcAddress("eglGetError"); platform->egl.CreatePbufferSurface = driver->getProcAddress("eglCreatePbufferSurface"); platform->egl.DestroySurface = driver->getProcAddress("eglDestroySurface"); platform->egl.SwapBuffers = driver->getProcAddress("eglSwapBuffers"); platform->egl.GetCurrentDisplay = driver->getProcAddress("eglGetCurrentDisplay"); platform->egl.GetCurrentSurface = driver->getProcAddress("eglGetCurrentSurface"); platform->egl.GetCurrentContext = driver->getProcAddress("eglGetCurrentContext"); platform->egl.MakeCurrent = driver->getProcAddress("eglMakeCurrent"); platform->egl.WaitGL = driver->getProcAddress("eglWaitGL"); platform->egl.WaitNative = driver->getProcAddress("eglWaitNative"); platform->egl.WaitClient = driver->getProcAddress("eglWaitClient"); platform->egl.ChooseConfig = driver->getProcAddress("eglChooseConfig"); platform->egl.GetConfigAttrib = driver->getProcAddress("eglGetConfigAttrib"); platform->egl.GetConfigs = driver->getProcAddress("eglGetConfigs"); platform->egl.QueryDeviceAttribEXT = driver->getProcAddress("eglQueryDeviceAttribEXT"); platform->egl.QueryDeviceStringEXT = driver->getProcAddress("eglQueryDeviceStringEXT"); platform->egl.QueryDevicesEXT = driver->getProcAddress("eglQueryDevicesEXT"); platform->egl.QueryDisplayAttribEXT = driver->getProcAddress("eglQueryDisplayAttribEXT"); // Optional functions. platform->egl.SwapBuffersWithDamage = driver->getProcAddress("eglSwapBuffersWithDamageKHR"); if (platform->egl.SwapBuffersWithDamage == NULL) { platform->egl.SwapBuffersWithDamage = driver->getProcAddress("eglSwapBuffersWithDamageEXT"); } platform->egl.CreateStreamProducerSurfaceKHR = driver->getProcAddress("CreateStreamProducerSurfaceKHR"); if (platform->egl.QueryString == NULL || platform->egl.QueryString == NULL || platform->egl.GetPlatformDisplay == NULL || platform->egl.Initialize == NULL || platform->egl.Terminate == NULL || platform->egl.GetError == NULL || platform->egl.CreatePbufferSurface == NULL || platform->egl.DestroySurface == NULL || platform->egl.SwapBuffers == NULL || platform->egl.GetCurrentDisplay == NULL || platform->egl.GetCurrentSurface == NULL || platform->egl.GetCurrentContext == NULL || platform->egl.MakeCurrent == NULL || platform->egl.ChooseConfig == NULL || platform->egl.GetConfigAttrib == NULL || platform->egl.GetConfigs == NULL || platform->egl.QueryDeviceAttribEXT == NULL || platform->egl.QueryDeviceStringEXT == NULL || platform->egl.QueryDevicesEXT == NULL || platform->egl.QueryDisplayAttribEXT == NULL) { eplPlatformDataUnref(platform); return NULL; } // Check for any extensions that we care about. str = platform->egl.QueryString(EGL_NO_DISPLAY, EGL_EXTENSIONS); platform->extensions.display_reference = eplFindExtension("EGL_KHR_display_reference", str); extplatform->version.major = EGL_EXTERNAL_PLATFORM_VERSION_MAJOR; extplatform->version.minor = EGL_EXTERNAL_PLATFORM_VERSION_MINOR; extplatform->version.micro = 0; extplatform->platform = platform_enum; extplatform->exports.unloadEGLExternalPlatform = eplUnloadExternalPlatformExport; extplatform->exports.getHookAddress = eplGetHookAddressExport; extplatform->exports.isValidNativeDisplay = eplIsValidNativeDisplayExport; extplatform->exports.getPlatformDisplay = eplGetPlatformDisplayExport; extplatform->exports.queryString = eplQueryStringExport; extplatform->exports.getInternalHandle = eplGetInternalHandleExport; //extplatform->exports.getObjectLabel = eplGetObjectLabelExport; extplatform->data = platform; return platform; } void eplPlatformBaseInitFinish(EplPlatformData *plat) { pthread_mutex_lock(&platform_data_list_mutex); glvnd_list_add(&plat->entry, &platform_data_list); pthread_mutex_unlock(&platform_data_list_mutex); } void eplPlatformBaseInitFail(EplPlatformData *plat) { assert(glvnd_list_is_empty(&plat->entry)); eplPlatformDataUnref(plat); } static EGLBoolean eplUnloadExternalPlatformExport(void *platformData) { EplPlatformData *platform = platformData; EplDisplay *pdpy, *pdpyTmp; if (platform == NULL) { return EGL_TRUE; } pthread_mutex_lock(&platform_data_list_mutex); glvnd_list_del(&platform->entry); pthread_mutex_unlock(&platform_data_list_mutex); platform->destroyed = EGL_TRUE; pthread_mutex_lock(&display_list_mutex); glvnd_list_for_each_entry_safe(pdpy, pdpyTmp, &display_list, entry) { if (pdpy->platform != platform) { continue; } pthread_mutex_lock(&pdpy->mutex); // Remove the display from the list and decrement its refcount. glvnd_list_del(&pdpy->entry); pthread_mutex_unlock(&pdpy->mutex); // Note that if some other thread is still holding a reference to this // display, then it might get leaked. // TODO: Should we just unconditionally free the display here? If // another thread is in the middle of a function call, then it's going // to crash anyway. if (eplRefCountUnref(&pdpy->refcount)) { DestroyDisplay(pdpy); } } pthread_mutex_unlock(&display_list_mutex); // Free the internal display list. Note that the driver will already have // terminated all of the internal eglDisplays. while (!glvnd_list_is_empty(&platform->internal_display_list)) { EplInternalDisplay *idpy = glvnd_list_first_entry(&platform->internal_display_list, EplInternalDisplay, entry); glvnd_list_del(&idpy->entry); idpy->edpy = EGL_NO_DISPLAY; eplInternalDisplayUnref(idpy); } if (platform->impl->CleanupPlatform != NULL) { platform->impl->CleanupPlatform(platform); } eplPlatformDataUnref(platform); return EGL_FALSE; } /** * This looks up and locks an EGLDisplay, but it does not check whether the * display is initialized. */ static EplDisplay *eplLockDisplayInternal(EGLDisplay edpy) { EplDisplay *pdpy = NULL; EplDisplay *node = NULL; if (edpy == EGL_NO_DISPLAY) { return NULL; } pthread_mutex_lock(&display_list_mutex); glvnd_list_for_each_entry(node, &display_list, entry) { if (node->external_display == edpy) { pdpy = node; break; } } if (pdpy == NULL) { pthread_mutex_unlock(&display_list_mutex); return NULL; } pthread_mutex_lock(&pdpy->mutex); eplRefCountRef(&pdpy->refcount); pdpy->use_count++; pthread_mutex_unlock(&display_list_mutex); return pdpy; } EplDisplay *eplDisplayAcquire(EGLDisplay edpy) { EplDisplay *pdpy = eplLockDisplayInternal(edpy); if (pdpy == NULL) { return NULL; } if (!pdpy->initialized) { eplSetError(pdpy->platform, EGL_NOT_INITIALIZED, "EGLDisplay %p is not initialized", edpy); eplDisplayRelease(pdpy); return NULL; } return pdpy; } EGLDisplay eplGetCurrentDisplay(void) { EplPlatformData *plat; EGLDisplay edpy = EGL_NO_DISPLAY; /* * In practice, loadEGLExternalPlatform is only ever going to get called * once (and if it was more than once, it would still be from the same * driver), so we'll only have one eglGetCurrentDisplay implementation. * * But, if there ever is the chance of getting loaded by more than one * driver, only one of them could have a current context, so the following * code would still work. */ pthread_mutex_lock(&platform_data_list_mutex); glvnd_list_for_each_entry(plat, &platform_data_list, entry) { edpy = plat->egl.GetCurrentDisplay(); if (edpy != EGL_NO_DISPLAY) { break; } } pthread_mutex_unlock(&platform_data_list_mutex); return edpy; } static void DestroyAllSurfaces(EplDisplay *pdpy) { while (!glvnd_list_is_empty(&pdpy->surface_list)) { EplSurface *psurf = glvnd_list_first_entry(&pdpy->surface_list, EplSurface, entry); // Bump the refcount, as if we'd called eglSurfaceAcquire, so that // eplSurfaceRelease works below. eplRefCountRef(&psurf->refcount); DeleteSurfaceCommon(pdpy, psurf); eplSurfaceRelease(pdpy, psurf); } } static void DestroyDisplay(EplDisplay *pdpy) { assert(pdpy != NULL); assert(pdpy->refcount.refcount == 0); DestroyAllSurfaces(pdpy); pdpy->platform->impl->CleanupDisplay(pdpy); pthread_mutex_destroy(&pdpy->mutex); eplPlatformDataUnref(pdpy->platform); free(pdpy); } void eplDisplayRelease(EplDisplay *pdpy) { if (pdpy == NULL) { return; } pdpy->use_count--; CheckTerminateDisplay(pdpy); pthread_mutex_unlock(&pdpy->mutex); if (eplRefCountUnref(&pdpy->refcount)) { DestroyDisplay(pdpy); } } void eplDisplayUnlock(EplDisplay *pdpy) { pthread_mutex_unlock(&pdpy->mutex); } void eplDisplayLock(EplDisplay *pdpy) { pthread_mutex_lock(&pdpy->mutex); } EplInternalDisplay *eplLookupInternalDisplay(EplPlatformData *platform, EGLDisplay handle) { EplInternalDisplay *found = NULL; EplInternalDisplay *node = NULL; pthread_mutex_lock(&platform->internal_display_list_mutex); glvnd_list_for_each_entry(node, &platform->internal_display_list, entry) { if (node->edpy == handle) { found = node; break; } } if (found == NULL) { found = calloc(1, sizeof(EplInternalDisplay)); if (found != NULL) { eplRefCountInit(&found->refcount); found->edpy = handle; found->init_count = 0; glvnd_list_add(&found->entry, &platform->internal_display_list); } } pthread_mutex_unlock(&platform->internal_display_list_mutex); return found; } EplInternalDisplay *eplGetDeviceInternalDisplay(EplPlatformData *platform, EGLDeviceEXT dev) { static const EGLAttrib TRACK_REFS_ATTRIBS[] = { EGL_TRACK_REFERENCES_KHR, EGL_TRUE, EGL_NONE }; EGLDisplay handle = EGL_NO_DISPLAY; const EGLAttrib *attribs = NULL; if (platform->extensions.display_reference) { attribs = TRACK_REFS_ATTRIBS; } handle = platform->egl.GetPlatformDisplay(EGL_PLATFORM_DEVICE_EXT, dev, attribs); if (handle == EGL_NO_DISPLAY) { return EGL_NO_DISPLAY; } return eplLookupInternalDisplay(platform, handle); } EGLBoolean eplInitializeInternalDisplay(EplPlatformData *platform, EplInternalDisplay *idpy, EGLint *major, EGLint *minor) { if (idpy == NULL) { return EGL_FALSE; } pthread_mutex_lock(&platform->internal_display_list_mutex); if (idpy->init_count == 0) { if (!platform->egl.Initialize(idpy->edpy, &idpy->major, &idpy->minor)) { pthread_mutex_unlock(&platform->internal_display_list_mutex); return EGL_FALSE; } } idpy->init_count++; if (major != NULL) { *major = idpy->major; } if (minor != NULL) { *minor = idpy->minor; } pthread_mutex_unlock(&platform->internal_display_list_mutex); return EGL_TRUE; } EGLBoolean eplTerminateInternalDisplay(EplPlatformData *platform, EplInternalDisplay *idpy) { if (idpy == NULL) { return EGL_FALSE; } pthread_mutex_lock(&platform->internal_display_list_mutex); if (idpy->init_count > 0) { if (idpy->init_count == 1) { if (!platform->egl.Terminate(idpy->edpy)) { pthread_mutex_unlock(&platform->internal_display_list_mutex); return EGL_FALSE; } } idpy->init_count--; } pthread_mutex_unlock(&platform->internal_display_list_mutex); return EGL_TRUE; } EplSurface *eplSurfaceAcquire(EplDisplay *pdpy, EGLSurface esurf) { EplSurface *psurf; EplSurface *found = NULL; if (pdpy == NULL || esurf == EGL_NO_SURFACE) { return NULL; } glvnd_list_for_each_entry(psurf, &pdpy->surface_list, entry) { if (psurf->external_surface == esurf) { found = psurf; break; } } if (found != NULL) { eplRefCountRef(&found->refcount); } return found; } void eplSurfaceRelease(EplDisplay *pdpy, EplSurface *psurf) { if (psurf != NULL) { if (eplRefCountUnref(&psurf->refcount)) { // If the refcount is zero, then that means eglDestroySurface or // eglTerminate has already run, so the platform-specific code has // already cleaned up the surface. assert(psurf->deleted); pdpy->platform->impl->FreeSurface(pdpy, psurf); FreeBaseSurface(psurf); } } } static EGLDisplay eplGetPlatformDisplayExport(void *platformData, EGLenum platform, void *nativeDisplay, const EGLAttrib* attribs) { EplPlatformData *plat = platformData; EGLAttrib *remainingAttribs = NULL; EplDisplay *pdpy = NULL; EplDisplay *node; EGLDisplay ret = EGL_NO_DISPLAY; int attribCount = 0; int attribIndex = 0; int i; EGLBoolean track_references = EGL_FALSE; if (platform != plat->platform_enum) { return EGL_NO_DISPLAY; } // First, make a copy of the attribs array, and pull out any attributes // that we care about. if (attribs != NULL) { while (attribs[attribCount] != EGL_NONE) { attribCount += 2; } } remainingAttribs = alloca((attribCount + 1) * sizeof(EGLAttrib)); for (i=0; iimpl->IsSameDisplay == NULL) { /* * If we don't have an IsSameDisplay function, then the * platform doesn't support any additional attributes. */ eplSetError(plat, EGL_BAD_ATTRIBUTE, "Unsupported attribute 0x%04llx", (unsigned long long) attribs[i]); return EGL_NO_DISPLAY; } remainingAttribs[attribIndex++] = attribs[i]; remainingAttribs[attribIndex++] = attribs[i + 1]; } } remainingAttribs[attribIndex] = EGL_NONE; pthread_mutex_lock(&display_list_mutex); glvnd_list_for_each_entry(node, &display_list, entry) { if (node->track_references != track_references) { continue; } if (node->native_display != nativeDisplay) { continue; } if (plat->impl->IsSameDisplay != NULL) { if (!plat->impl->IsSameDisplay(plat, node, platform, nativeDisplay, remainingAttribs)) { continue; } } // At this point, either IsSameDisplay returned true, or we don't have // any additional attributes beyond what the platform base code handles. pdpy = node; break; } if (pdpy != NULL) { // We found a matching display, so return it ret = pdpy->external_display; goto done; } pdpy = calloc(1, sizeof(EplDisplay)); if (pdpy == NULL) { eplSetError(plat, EGL_BAD_ALLOC, "Out of memory"); goto done; } if (!eplInitRecursiveMutex(&pdpy->mutex)) { eplSetError(plat, EGL_BAD_ALLOC, "Failed to create internal mutex"); free(pdpy); goto done; } pdpy->platform = eplPlatformDataRef(plat); pdpy->platform_enum = platform; pdpy->external_display = (EGLDisplay) pdpy; pdpy->track_references = track_references; pdpy->native_display = nativeDisplay; glvnd_list_init(&pdpy->surface_list); glvnd_list_init(&pdpy->entry); if (!plat->impl->GetPlatformDisplay(plat, pdpy, nativeDisplay, remainingAttribs, &display_list)) { pthread_mutex_destroy(&pdpy->mutex); eplPlatformDataUnref(pdpy->platform); free(pdpy); ret = EGL_NO_DISPLAY; goto done; } eplRefCountInit(&pdpy->refcount); glvnd_list_add(&pdpy->entry, &display_list); ret = pdpy->external_display; done: pthread_mutex_unlock(&display_list_mutex); return ret; } static EGLBoolean HookInitialize(EGLDisplay edpy, EGLint *major, EGLint *minor) { EplDisplay *pdpy = eplLockDisplayInternal(edpy); if (pdpy == NULL) { return EGL_FALSE; } if (!pdpy->initialized) { pdpy->major = 1; pdpy->minor = 5; if (!pdpy->platform->impl->InitializeDisplay(pdpy->platform, pdpy, &pdpy->major, &pdpy->minor)) { eplDisplayRelease(pdpy); return EGL_FALSE; } pdpy->initialized = EGL_TRUE; pdpy->init_count = 1; } else { if (pdpy->track_references) { pdpy->init_count++; } else { pdpy->init_count = 1; } } if (major != NULL) { *major = pdpy->major; } if (minor != NULL) { *minor = pdpy->minor; } eplDisplayRelease(pdpy); return EGL_TRUE; } static void TerminateDisplay(EplDisplay *pdpy) { pdpy->init_count = 0; pdpy->initialized = EGL_FALSE; if (pdpy->platform == NULL) { // We've already gone through teardown, so don't try to do anything // else. All remaining cleanup will happen in DestroyDisplay. return; } DestroyAllSurfaces(pdpy); pdpy->platform->impl->TerminateDisplay(pdpy->platform, pdpy); } static void CheckTerminateDisplay(EplDisplay *pdpy) { if (pdpy->initialized) { if (pdpy->init_count == 0 && pdpy->use_count == 0) { TerminateDisplay(pdpy); } } } static EGLBoolean HookTerminate(EGLDisplay edpy) { EplDisplay *pdpy = eplLockDisplayInternal(edpy); if (pdpy == NULL) { return EGL_FALSE; } if (pdpy->init_count > 0) { pdpy->init_count--; } eplDisplayRelease(pdpy); return EGL_TRUE; } static EGLAttrib *ConvertIntAttribs(const EGLint *int_attribs) { EGLAttrib *attribs = NULL; int count = 0; int i; if (int_attribs == NULL) { return NULL; } for (count = 0; int_attribs[count] != EGL_NONE; count += 2) { } attribs = malloc((count + 1) * sizeof(EGLAttrib)); if (attribs == NULL) { return NULL; } for (i=0; iplatform); if (psurf == NULL) { eplDisplayRelease(pdpy); return EGL_NO_SURFACE; } psurf->type = type; if (type == EPL_SURFACE_TYPE_WINDOW) { if (pdpy->platform->impl->CreateWindowSurface != NULL) { psurf->internal_surface = pdpy->platform->impl->CreateWindowSurface(pdpy->platform, pdpy, psurf, config, native_handle, attrib_list, create_platform); } else { eplSetError(pdpy->platform, EGL_BAD_ALLOC, "Window surfaces are not supported"); } } else if (type == EPL_SURFACE_TYPE_PIXMAP) { if (pdpy->platform->impl->CreatePixmapSurface != NULL) { psurf->internal_surface = pdpy->platform->impl->CreatePixmapSurface(pdpy->platform, pdpy, psurf, config, native_handle, attrib_list, create_platform); } else { eplSetError(pdpy->platform, EGL_BAD_ALLOC, "Pixmap surfaces are not supported"); } } else { assert(!"Can't happen: Invalid surface type"); psurf->internal_surface = EGL_NO_SURFACE; } if (psurf->internal_surface != EGL_NO_SURFACE) { psurf->external_surface = (EGLSurface) psurf; ret = psurf->external_surface; eplRefCountRef(&psurf->refcount); glvnd_list_add(&psurf->entry, &pdpy->surface_list); } else { FreeBaseSurface(psurf); } return ret; } static EGLSurface HookCreatePlatformWindowSurface(EGLDisplay edpy, EGLConfig config, void *native_window, const EGLAttrib *attrib_list) { EplDisplay *pdpy = eplDisplayAcquire(edpy); EGLSurface esurf; if (pdpy == NULL) { return EGL_NO_SURFACE; } esurf = CommonCreateSurface(edpy, config, native_window, attrib_list, EPL_SURFACE_TYPE_WINDOW, EGL_TRUE); eplDisplayRelease(pdpy); return esurf; } static EGLSurface HookCreateWindowSurface(EGLDisplay edpy, EGLConfig config, EGLNativeWindowType win, const EGLint *attrib_list) { EplDisplay *pdpy = eplDisplayAcquire(edpy); EGLAttrib *attribs = NULL; EGLSurface esurf; if (pdpy == NULL) { return EGL_NO_SURFACE; } attribs = ConvertIntAttribs(attrib_list); if (attribs == NULL && attrib_list != NULL) { eplSetError(pdpy->platform, EGL_BAD_ALLOC, "Out of memory"); eplDisplayRelease(pdpy); return EGL_NO_DISPLAY; } esurf = CommonCreateSurface(edpy, config, (void *) win, attribs, EPL_SURFACE_TYPE_WINDOW, EGL_FALSE); free(attribs); eplDisplayRelease(pdpy); return esurf; } static EGLSurface HookCreatePlatformPixmapSurface(EGLDisplay edpy, EGLConfig config, void *native_pixmap, const EGLAttrib *attrib_list) { EplDisplay *pdpy = eplDisplayAcquire(edpy); EGLSurface esurf; if (pdpy == NULL) { return EGL_NO_SURFACE; } esurf = CommonCreateSurface(edpy, config, native_pixmap, attrib_list, EPL_SURFACE_TYPE_PIXMAP, EGL_TRUE); eplDisplayRelease(pdpy); return esurf; } static EGLSurface HookCreatePixmapSurface(EGLDisplay edpy, EGLConfig config, EGLNativePixmapType win, const EGLint *attrib_list) { EplDisplay *pdpy = eplDisplayAcquire(edpy); EGLAttrib *attribs = NULL; EGLSurface esurf; if (pdpy == NULL) { return EGL_NO_SURFACE; } attribs = ConvertIntAttribs(attrib_list); if (attribs == NULL && attrib_list != NULL) { eplSetError(pdpy->platform, EGL_BAD_ALLOC, "Out of memory"); eplDisplayRelease(pdpy); return EGL_NO_DISPLAY; } esurf = CommonCreateSurface(edpy, config, (void *) win, attribs, EPL_SURFACE_TYPE_PIXMAP, EGL_FALSE); free(attribs); eplDisplayRelease(pdpy); return esurf; } static EGLSurface HookCreatePbufferSurface(EGLDisplay edpy, EGLConfig config, const EGLint *attrib_list) { EplDisplay *pdpy = NULL; EGLSurface esurf; pdpy = eplDisplayAcquire(edpy); if (pdpy == NULL) { return EGL_NO_SURFACE; } // The driver requires that we provide a hook for eglCreatePbufferSurface, // but we can just pass it through to the driver. esurf = pdpy->platform->egl.CreatePbufferSurface(pdpy->internal_display, config, attrib_list); eplDisplayRelease(pdpy); return esurf; } static void DeleteSurfaceCommon(EplDisplay *pdpy, EplSurface *psurf) { assert(!psurf->deleted); if (!psurf->deleted) { psurf->deleted = EGL_TRUE; glvnd_list_del(&psurf->entry); pdpy->platform->impl->DestroySurface(pdpy, psurf); eplRefCountUnref(&psurf->refcount); } } static EGLBoolean HookDestroySurface(EGLDisplay edpy, EGLSurface esurf) { EplDisplay *pdpy; EplSurface *psurf; EGLBoolean ret = EGL_FALSE; pdpy = eplDisplayAcquire(edpy); if (pdpy == NULL) { return EGL_FALSE; } psurf = eplSurfaceAcquire(pdpy, esurf); if (psurf != NULL) { DeleteSurfaceCommon(pdpy, psurf); eplSurfaceRelease(pdpy, psurf); ret = EGL_TRUE; } else { // This EGLSurface doesn't belong to the platform library, so just pass // it on to the driver. ret = pdpy->platform->egl.DestroySurface(pdpy->internal_display, esurf); } eplDisplayRelease(pdpy); return ret; } static EGLBoolean HookSwapBuffersWithDamage(EGLDisplay edpy, EGLSurface esurf, const EGLint *rects, EGLint n_rects) { EplDisplay *pdpy; EplSurface *psurf; EGLBoolean ret = EGL_FALSE; pdpy = eplDisplayAcquire(edpy); if (pdpy == NULL) { return EGL_FALSE; } if (pdpy->platform->egl.GetCurrentDisplay() != edpy) { eplSetError(pdpy->platform, EGL_BAD_SURFACE, "EGLDisplay %p is not current", edpy); eplDisplayRelease(pdpy); return EGL_FALSE; } psurf = eplSurfaceAcquire(pdpy, esurf); if (psurf != NULL) { if (psurf->type != EPL_SURFACE_TYPE_WINDOW) { eplSetError(pdpy->platform, EGL_BAD_SURFACE, "EGLSurface %p is not a window", esurf); ret = EGL_FALSE; } else if (pdpy->platform->egl.GetCurrentSurface(EGL_DRAW) != esurf) { eplSetError(pdpy->platform, EGL_BAD_SURFACE, "EGLSurface %p is not current", esurf); ret = EGL_FALSE; } else { ret = pdpy->platform->impl->SwapBuffers(pdpy->platform, pdpy, psurf, rects, n_rects); } eplSurfaceRelease(pdpy, psurf); eplDisplayRelease(pdpy); } else { // If we don't recognize this EGLSurface, then it might be a pbuffer or // stream, so just pass it through to the driver. EGLDisplay internal = pdpy->internal_display; PFNEGLSWAPBUFFERSWITHDAMAGEKHRPROC SwapBuffersWithDamage = pdpy->platform->egl.SwapBuffersWithDamage; PFNEGLSWAPBUFFERSPROC SwapBuffers = pdpy->platform->egl.SwapBuffers; // Release the display before calling into the driver, so that we don't // sit on the lock for a (potentially long) SwapBuffers operation. eplDisplayRelease(pdpy); if (SwapBuffersWithDamage != NULL && rects != NULL && n_rects > 0) { ret = SwapBuffersWithDamage(internal, esurf, rects, n_rects); } else { ret = SwapBuffers(internal, esurf); } return ret; } return ret; } static EGLBoolean HookSwapBuffers(EGLDisplay edpy, EGLSurface esurf) { return HookSwapBuffersWithDamage(edpy, esurf, NULL, 0); } static EGLBoolean HookWaitGL(void) { EGLDisplay edpy = eplGetCurrentDisplay(); EplDisplay *pdpy = eplDisplayAcquire(edpy); EGLBoolean ret = EGL_FALSE; if (pdpy == NULL) { return EGL_FALSE; } assert(pdpy->platform->impl->WaitGL != NULL); if (pdpy->platform->impl->WaitGL != NULL) { EplSurface *psurf = eplSurfaceAcquire(pdpy, pdpy->platform->egl.GetCurrentSurface(EGL_DRAW)); ret = pdpy->platform->impl->WaitGL(pdpy, psurf); eplSurfaceRelease(pdpy, psurf); } else { // This shouldn't happen, because we only provide an eglWaitGL hook if // we have an implementation. But, if we wanted to handle this case, // then we could just forward the call through to the driver. eplSetError(pdpy->platform, EGL_BAD_ALLOC, "Internal error: eglWaitGL hook should not be called"); eplDisplayRelease(pdpy); return EGL_FALSE; } eplDisplayRelease(pdpy); return ret; } static EGLBoolean HookWaitNative(void) { EGLDisplay edpy = eplGetCurrentDisplay(); EplDisplay *pdpy = eplDisplayAcquire(edpy); EGLBoolean ret = EGL_FALSE; if (pdpy == NULL) { return EGL_FALSE; } assert(pdpy->platform->impl->WaitNative != NULL); if (pdpy->platform->impl->WaitNative != NULL) { EplSurface *psurf = eplSurfaceAcquire(pdpy, pdpy->platform->egl.GetCurrentSurface(EGL_DRAW)); ret = pdpy->platform->impl->WaitNative(pdpy, psurf); eplSurfaceRelease(pdpy, psurf); } else { eplSetError(pdpy->platform, EGL_BAD_ALLOC, "Internal error: eglWaitNative hook should not be called"); eplDisplayRelease(pdpy); return EGL_FALSE; } eplDisplayRelease(pdpy); return ret; } static const EplHookFunc BASE_HOOK_FUNCTIONS[] = { { "eglCreatePbufferSurface", HookCreatePbufferSurface }, { "eglCreatePixmapSurface", HookCreatePixmapSurface }, { "eglCreatePlatformPixmapSurface", HookCreatePlatformPixmapSurface }, { "eglCreatePlatformWindowSurface", HookCreatePlatformWindowSurface }, { "eglCreateWindowSurface", HookCreateWindowSurface }, { "eglDestroySurface", HookDestroySurface }, { "eglInitialize", HookInitialize }, { "eglSwapBuffers", HookSwapBuffers }, { "eglSwapBuffersWithDamageEXT", HookSwapBuffersWithDamage }, { "eglSwapBuffersWithDamageKHR", HookSwapBuffersWithDamage }, { "eglTerminate", HookTerminate }, }; static const size_t BASE_HOOK_FUNCTION_COUNT = sizeof(BASE_HOOK_FUNCTIONS) / sizeof(BASE_HOOK_FUNCTIONS[0]); void *eplGetHookAddressExport(void *platformData, const char *name) { EplPlatformData *plat = platformData; void *func = NULL; func = eplFindHookFunction(BASE_HOOK_FUNCTIONS, BASE_HOOK_FUNCTION_COUNT, name); if (func != NULL) { return func; } if (plat->impl->GetHookFunction != NULL) { func = plat->impl->GetHookFunction(plat, name); if (func != NULL) { return func; } } if (plat->impl->WaitGL != NULL && strcmp(name, "eglWaitGL") == 0) { return HookWaitGL; } if (plat->impl->WaitNative != NULL && strcmp(name, "eglWaitNative") == 0) { return HookWaitNative; } return NULL; } static EGLBoolean eplIsValidNativeDisplayExport(void *platformData, void *nativeDisplay) { EplPlatformData *plat = platformData; if (plat->impl->IsValidNativeDisplay != NULL) { return plat->impl->IsValidNativeDisplay(platformData, nativeDisplay); } else { return EGL_FALSE; } } static const char *eplQueryStringExport(void *platformData, EGLDisplay edpy, EGLExtPlatformString name) { EplPlatformData *plat = platformData; EplDisplay *pdpy = NULL; const char *str; if (edpy != EGL_NO_DISPLAY) { pdpy = eplDisplayAcquire(edpy); if (pdpy == NULL) { return NULL; } } str = plat->impl->QueryString(plat, pdpy, name); if (pdpy != NULL) { eplDisplayRelease(pdpy); } return str; } static void *eplGetInternalHandleExport(EGLDisplay edpy, EGLenum type, void *handle) { void *ret = NULL; if (type == EGL_OBJECT_DISPLAY_KHR) { EplDisplay *pdpy = eplLockDisplayInternal(handle); if (pdpy != NULL) { ret = pdpy->internal_display; eplDisplayRelease(pdpy); } } else { EplDisplay *pdpy = eplLockDisplayInternal(edpy); if (pdpy != NULL) { if (type == EGL_OBJECT_SURFACE_KHR) { EplSurface *psurf = eplSurfaceAcquire(pdpy, (EGLSurface) handle); if (psurf != NULL) { ret = psurf->internal_surface; eplSurfaceRelease(pdpy, psurf); } else { /* * Assume that if we don't recognize the handle, then it's * a pbuffer or stream surface, and so the driver should * just pass it through. If the handle is invalid, then the * driver should then set the appropriate error code on its * own. */ ret = handle; } } eplDisplayRelease(pdpy); } } return ret; } void eplSetError(EplPlatformData *platform, EGLint error, const char *fmt, ...) { EGLint messageType; const char *message = NULL; char buf[1024]; va_list args; if (error == EGL_BAD_ALLOC) { messageType = EGL_DEBUG_MSG_CRITICAL_KHR; } else { messageType = EGL_DEBUG_MSG_ERROR_KHR; } if (fmt != NULL) { va_start(args, fmt); vsnprintf(buf, sizeof(buf), fmt, args); va_end(args); message = buf; } platform->callbacks.setError(error, messageType, message); } EGLBoolean eplSwitchCurrentSurface(EplPlatformData *platform, EplDisplay *pdpy, EGLSurface old_surface, EGLSurface new_surface) { EGLSurface new_draw = EGL_NO_SURFACE; EGLSurface new_read = EGL_NO_SURFACE; if (platform->egl.GetCurrentDisplay() != pdpy->internal_display) { // The display isn't current, so the surface can't be. return EGL_TRUE; } new_draw = platform->egl.GetCurrentSurface(EGL_DRAW); new_read = platform->egl.GetCurrentSurface(EGL_READ); if (new_draw != old_surface && new_read != old_surface) { return EGL_TRUE; } if (new_draw == old_surface) { new_draw = new_surface; } if (new_read == old_surface) { new_read = new_surface; } return platform->egl.MakeCurrent(platform->egl.GetCurrentDisplay(), new_draw, new_read, platform->egl.GetCurrentContext()); } EGLDeviceEXT *eplGetAllDevices(EplPlatformData *platform, EGLint *ret_count) { EGLint count = 0; EGLDeviceEXT *devices = NULL; if (!platform->egl.QueryDevicesEXT(0, NULL, &count)) { return NULL; } devices = malloc((count + 1) * sizeof(EGLDeviceEXT)); if (devices == NULL) { eplSetError(platform, EGL_BAD_ALLOC, "Out of memory"); return NULL; } if (count > 0) { if (!platform->egl.QueryDevicesEXT(count, devices, &count)) { free(devices); return NULL; } } devices[count] = EGL_NO_DEVICE_EXT; if (ret_count != NULL) { *ret_count = count; } return devices; } struct glvnd_list *eplLockDisplayList(void) { pthread_mutex_lock(&display_list_mutex); return &display_list; } void eplUnlockDisplayList(void) { pthread_mutex_unlock(&display_list_mutex); } egl-x11-1.0.0/src/base/platform-base.h000066400000000000000000000274521471743126200173150ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: Copyright (c) 2024 NVIDIA CORPORATION & AFFILIATES. All rights reserved. * SPDX-License-Identifier: Apache-2.0 * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef PLATFORM_BASE_H #define PLATFORM_BASE_H /** * \file * * Common bookkeeping and infrastructure for an EGL platform library. * * These functions handle the basic tasks of keeping track of internal and * external EGLDisplays and EGLSurfaces. */ #include #include #include #include #include "glvnd_list.h" #include "refcountobj.h" #define PUBLIC __attribute__((visibility("default"))) #ifdef __cplusplus extern "C" { #endif // Opaque types for private implementation data. typedef struct _EplImplSurface EplImplSurface; typedef struct _EplImplDisplay EplImplDisplay; typedef struct _EplImplPlatform EplImplPlatform; typedef enum { EPL_SURFACE_TYPE_WINDOW, EPL_SURFACE_TYPE_PIXMAP, } EplSurfaceType; /** * Keeps track of an internal EGLDisplay. */ typedef struct { EplRefCount refcount; EGLDisplay edpy; /** * The number of times that this display has been initialized. This is used * to simulate the EGL_KHR_display_reference extension even if the * underlying driver doesn't support it. */ unsigned int init_count; EGLint major; EGLint minor; struct glvnd_list entry; } EplInternalDisplay; /** * Keeps track of an EGLSurface. */ typedef struct { EplRefCount refcount; EGLSurface external_surface; EGLSurface internal_surface; EplSurfaceType type; EGLBoolean deleted; /** * Private data used by the implementation. */ EplImplSurface *priv; struct glvnd_list entry; } EplSurface; /** * Keeps track of data for an external (application-facing) EGLDisplay. */ typedef struct { /** * A reference count. This is used so that we know when it's safe to free * EplDisplay struct. * * Since EGLDisplays can't be destroyed (yet), this only really matters if * we go through teardown while another thread is still using the * EplDisplay. It'll be more interesting once we add support for * EGL_EXT_display_alloc. */ EplRefCount refcount; /** * The external (application-facing) EGLDisplay handle. */ EGLDisplay external_display; /** * The internal EGLDisplay handle. */ EGLDisplay internal_display; /** * The platform enum (EGL_PLATFORM_X11_KHR, etc.). */ EGLenum platform_enum; /** * The native display that this EplDisplay was created from. */ void *native_display; /** * A pointer back to the EplPlatformData struct that owns this EplDisplay. * * This is needed because most of the hook functions don't get a separate * parameter for the EplPlatformData. */ struct _EplPlatformData *platform; /** * All of the existing EplSurface structs. */ struct glvnd_list surface_list; /** * Private data for the implementation. */ EplImplDisplay *priv; // Everything after this in EplDisplay should be treated as internal to // platform-base.c. /** * A mutex to control access to the display. This is a recursive mutex. */ pthread_mutex_t mutex; /** * True if this display was created with EGL_TRACK_REFERENCES set. */ EGLBoolean track_references; /** * The number of times that the display has been initialized. If this * display was not created with EGL_TRACK_REFERENCES set, then this is * capped at 1. */ unsigned int init_count; /** * This is a counter to keep track of whether the display is in use or not. * * If the app calls eglTerminate, then we defer the termination until the * display is no longer in use. */ unsigned int use_count; /// The major version number for eglInitialize in this context. EGLint major; /// The minor version number for eglInitialize in this context. EGLint minor; /// True if this display has been initialized. EGLBoolean initialized; struct glvnd_list entry; } EplDisplay; typedef struct _EplPlatformData { EplRefCount refcount; struct { PFNEGLQUERYSTRINGPROC QueryString; PFNEGLGETPLATFORMDISPLAYPROC GetPlatformDisplay; PFNEGLINITIALIZEPROC Initialize; PFNEGLTERMINATEPROC Terminate; PFNEGLGETERRORPROC GetError; PFNEGLCREATEPBUFFERSURFACEPROC CreatePbufferSurface; PFNEGLDESTROYSURFACEPROC DestroySurface; PFNEGLSWAPBUFFERSPROC SwapBuffers; PFNEGLCHOOSECONFIGPROC ChooseConfig; PFNEGLGETCONFIGATTRIBPROC GetConfigAttrib; PFNEGLGETCONFIGSPROC GetConfigs; PFNEGLGETCURRENTDISPLAYPROC GetCurrentDisplay; PFNEGLGETCURRENTSURFACEPROC GetCurrentSurface; PFNEGLGETCURRENTCONTEXTPROC GetCurrentContext; PFNEGLMAKECURRENTPROC MakeCurrent; PFNEGLWAITGLPROC WaitGL; PFNEGLWAITCLIENTPROC WaitClient; PFNEGLWAITNATIVEPROC WaitNative; PFNEGLQUERYDEVICEATTRIBEXTPROC QueryDeviceAttribEXT; PFNEGLQUERYDEVICESTRINGEXTPROC QueryDeviceStringEXT; PFNEGLQUERYDEVICESEXTPROC QueryDevicesEXT; PFNEGLQUERYDISPLAYATTRIBEXTPROC QueryDisplayAttribEXT; PFNEGLSWAPBUFFERSWITHDAMAGEKHRPROC SwapBuffersWithDamage; PFNEGLCREATESTREAMPRODUCERSURFACEKHRPROC CreateStreamProducerSurfaceKHR; } egl; struct { EGLBoolean display_reference; } extensions; struct { PEGLEXTFNGETPROCADDRESS getProcAddress; PEGLEXTFNDEBUGMESSAGE debugMessage; PEGLEXTFNSETERROR setError; } callbacks; /** * True if we're going through teardown for this platform. Once we're in * teardown, it's no longer safe to call into the driver. * * Note that if another thread is currently calling an EGL function when * the platform library gets torn down, then things are likely to break no * matter what, because the driver will have finished a lot of its teardown * before the platform library finds out about it. * * Thus, this flag is only to make it easier to share cleanup code between * platform library teardown and eglDestroySurface et. al. */ EGLBoolean destroyed; /** * Private data for the implementation. */ EplImplPlatform *priv; struct glvnd_list internal_display_list; pthread_mutex_t internal_display_list_mutex; EGLenum platform_enum; const struct _EplImplFuncs *impl; struct glvnd_list entry; } EplPlatformData; EPL_REFCOUNT_DECLARE_TYPE_FUNCS(EplPlatformData, eplPlatformData); EPL_REFCOUNT_DECLARE_TYPE_FUNCS(EplInternalDisplay, eplInternalDisplay); /** * Allocates and initializes an EplPlatformData struct. * * This is called from the loadEGLExternalPlatform entrypoint. * * After calling eplPlatformBaseAllocate, the caller should perform any * platform-specific initialization, and then call eplPlatformBaseInitFinish * (on success) or eplPlatformBaseInitFail (on failure). * * \param platform_enum The EGL enum value for this platform. * \param impl The platform implementation functions. * \param platform_priv_size If non-zero, then allocate additional space and * assign it to EplPlatformData::priv. * \return A EplPlatformData struct, or NULL on error. */ EplPlatformData *eplPlatformBaseAllocate(int major, int minor, const EGLExtDriver *driver, EGLExtPlatform *extplatform, EGLenum platform_enum, const struct _EplImplFuncs *impl, size_t platform_priv_size); /** * Finishes initializing a platform. * * This function should be called from loadEGLExternalPlatform after any * platform-specific initialization. */ void eplPlatformBaseInitFinish(EplPlatformData *plat); /** * Cleans up a EplPlatformData after an init failure. * * This function should be called from loadEGLExternalPlatform if the * platform-specicic initialization fails. */ void eplPlatformBaseInitFail(EplPlatformData *plat); /** * Looks up an EplDisplay struct. * * This will look up the display, lock it, and check to make sure that it's * initialized. * * The caller must call eplDisplayRelease to unlock and release the display. */ EplDisplay *eplDisplayAcquire(EGLDisplay edpy); /** * Returns the current EGLDisplay for the current thread. */ EGLDisplay eplGetCurrentDisplay(void); /** * Releases a display acquired with eplDisplayAcquire. */ void eplDisplayRelease(EplDisplay *pdpy); /** * Unlocks the mutex for an EplDisplay, but does not decrement the reference * count. * * This allows a platform library to temporarily release the mutex for an * EplDisplay, but ensures that the EplDisplay itself sticks around. * * The caller must call eplDisplayLock to lock the mutex again before calling * eplDisplayRelease. */ void eplDisplayUnlock(EplDisplay *pdpy); /** * Re-locks the mutex for an EplDisplay. */ void eplDisplayLock(EplDisplay *pdpy); /** * Looks up an internal EGLDisplay. If an EplInternalDisplay struct doesn't * already exist, then it will be created and returned. */ EplInternalDisplay *eplLookupInternalDisplay(EplPlatformData *platform, EGLDisplay handle); /** * Returns an EplInternalDisplay struct for a device. * * This is just a convenience wrapper which creates an EGLDisplay from the * device and then calls eplLookupInternalDisplay. */ EplInternalDisplay *eplGetDeviceInternalDisplay(EplPlatformData *platform, EGLDeviceEXT dev); /** * Calls eglInitialize on an internal display. */ EGLBoolean eplInitializeInternalDisplay(EplPlatformData *platform, EplInternalDisplay *idpy, EGLint *major, EGLint *minor); /** * Calls eglTerminate on an internal display. */ EGLBoolean eplTerminateInternalDisplay(EplPlatformData *platform, EplInternalDisplay *idpy); /** * Sets the current EGL error, and issues a debug message. */ void eplSetError(EplPlatformData *platform, EGLint error, const char *fmt, ...); /** * Looks up the EplSurface struct for a surface. * * This will lock the surface and increment its refcount. * * The caller must release the surface with \c eplSurfaceRelease. * * Note that this might return NULL if the surface is a pbuffer or stream. */ EplSurface *eplSurfaceAcquire(EplDisplay *pdpy, EGLSurface esurf); /** * Decrements the refcount for an EplSurface and unlocks it. */ void eplSurfaceRelease(EplDisplay *pdpy, EplSurface *psurf); /** * Replaces the current surface. * * If \p old_surface is the current surface, then this will call eglMakeCurrent * to switch to \p new_surface. * * This is used to deal with stuff like window resizing, where we might need to * replace the internal EGLSurface handle for a surface. */ EGLBoolean eplSwitchCurrentSurface(EplPlatformData *platform, EplDisplay *pdpy, EGLSurface old_surface, EGLSurface new_surface); /** * Returns a NULL-terminated array of all available EGLDeviceEXT handles. * * The caller must free the array using free(). */ EGLDeviceEXT *eplGetAllDevices(EplPlatformData *platform, EGLint *ret_count); /** * Locks and returns the list of EplDisplay structs. * * This can be used to deal with the application closing a native display out * from under us. * * The caller must call eplUnlockDisplayList after it's finished. */ struct glvnd_list *eplLockDisplayList(void); void eplUnlockDisplayList(void); #ifdef __cplusplus } #endif #endif // PLATFORM_BASE_H egl-x11-1.0.0/src/base/platform-impl.h000066400000000000000000000253571471743126200173460ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: Copyright (c) 2024 NVIDIA CORPORATION & AFFILIATES. All rights reserved. * SPDX-License-Identifier: Apache-2.0 * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef PLATFORM_IMPL_H #define PLATFORM_IMPL_H /** * \file * * Functions that the platform implementation must implement. */ #include #include #include #include "platform-base.h" #ifdef __cplusplus extern "C" { #endif /** * A table of functions for the platform-specific implementation. */ typedef struct _EplImplFuncs { /** * Cleans up the platform data. */ void (* CleanupPlatform) (EplPlatformData *plat); /** * Handles the EGLExtPlatformExports::QueryString export. * * Note that \p pdpy will not be NULL. If the driver passes in an unknown * EGLDisplay, then the base library will simply return without calling * this function. That means that currently, there's no way to add an * extension to an EGLDisplay that the platform library doesn't own. * * \param pdpy The EplDisplay struct. */ const char * (* QueryString) (EplPlatformData *plat, EplDisplay *pdpy, EGLExtPlatformString name); /** * Checks if a pointer looks like a valid native display for a platform. * * This function is optional. If it's NULL, then it's equivalent to simply * returning EGL_FALSE. * * \param plat The EplPlatformData struct. * \param plat The native display pointer. * \return EGL_TRUE if \p nativeDisplay looks like a valid native display. */ EGLBoolean (* IsValidNativeDisplay) (EplPlatformData *plat, void *nativeDisplay); /** * Returns the hook function for an EGL function. * * This function is optional. If it's NULL, then there are no * platform-specific hook functions. * * \param plat The EplPlatformData struct. * \param name The EGL function name. * \return A function pointer, or NULL if there isn't aa hook for that * function. */ void * (* GetHookFunction) (EplPlatformData *plat, const char *name); /** * Checks if an eglGetPlatformDisplay call matches an existing EGLDisplay. * * Two eglGetPlatformDisplay calls with the same parameters are supposed to * return the same EGLDisplay. The base library checks the platform enum, * the native display pointer, and any attributes that the base library * itself handles. * * If there are additional platform-specific attributes, then this function * checks whether those attributes match an existing display, possibly * taking into account any default attribute values. * * If the implementation doesn't recognize an attribute, it may either * ignore the attribute or return EGL_FALSE. * * This function is optional. If it's NULL, then the platform does not * accept any platform-specific attributes, and so only the base library * checks described above apply. * * \param plat The EplPlatformData struct. * \param pdpy An existing EplDisplay struct to check. * \param platform The platform enum. * \param native_display The native display pointer. * \param attribs The remaining attributes. This array does not include * the attributes that the base library handles. * \return EGL_TRUE if the attributes match \p pdpy, and so * eglGetPlatformDisplay should return the EGLDisplay handle for it. */ EGLBoolean (* IsSameDisplay) (EplPlatformData *plat, EplDisplay *pdpy, EGLint platform, void *native_display, const EGLAttrib *attribs); /** * Called to implement eglGetPlatformDisplay. * * \param plat The EplPlatformData struct. * \param pdpy The base EplDisplay struct. This will be filled in already, * except for the \c internal_display member. * \param platform The platform enum. * \param native_display The native display pointer. * \param attribs The remaining attributes. This array does not include * the attributes that the base library handles. * * \return EGL_TRUE on success, or EGL_FALSE on failure. */ EGLBoolean (* GetPlatformDisplay) (EplPlatformData *plat, EplDisplay *pdpy, void *native_display, const EGLAttrib *attribs, struct glvnd_list *existing_displays); /** * Cleans up any implementation data in an EplDisplay. * * Currently, this is only called during teardown, but if/when * EGL_EXT_display_alloc is available, then this would be called to handle * destroying an EGLDisplay. * * If this is called during teardown, then the \c EplDisplay::platform pointer * may be NULL. * * Note that if a EGLDisplay is still initialized during teardown, then the * base library will call \c TerminateDisplay before \c CleanupDisplay. * * \param pdpy The EplDisplay struct to destroy. */ void (* CleanupDisplay) (EplDisplay *pdpy); /** * Called to implement eglInitialize. * * Note that the base library handles EGL_KHR_display_reference, so this * function is only ever called on an uninitialized display. * * \param plat The EplPlatformData struct. * \param pdpy The EplDisplay to initialize. * \param[out] major Returns the major version number. * \param[out] minor Returns the minor version number. * \return EGL_TRUE on success, EGL_FALSE on failure. */ EGLBoolean (* InitializeDisplay) (EplPlatformData *plat, EplDisplay *pdpy, EGLint *major, EGLint *minor); /** * Called to implement eglTerminate. * * Note that the base library handles EGL_KHR_display_reference, so this * function is only ever called on an initialized display. */ void (* TerminateDisplay) (EplPlatformData *plat, EplDisplay *pdpy); /** * Creates an EGLSurface for a window. * * This function is optional. If it's NULL, then the platform does not * support window surfaces. * * \param plat The EplPlatformData struct * \param pdpy The EplDisplay struct * \param psurf An EplSurface struct, with the base information filled in. * \param native_surface The native surface handle. * \param attribs The attribute list. * \param create_platform If this is true, then the call is from * eglCreatePlatformWindowSurface. If false, it's from * eglCreateWindowSurface. * \return The internal EGLSurface handle, or EGL_NO_SURFACE on failure. */ EGLSurface (* CreateWindowSurface) (EplPlatformData *plat, EplDisplay *pdpy, EplSurface *psurf, EGLConfig config, void *native_surface, const EGLAttrib *attribs, EGLBoolean create_platform); /** * Creates an EGLSurface for a pixmap. * * This function is optional. If it's NULL, then the platform does not * support pixmap surfaces. * * \param plat The EplPlatformData struct * \param pdpy The EplDisplay struct * \param psurf An EplSurface struct, with the base information filled in. * \param native_surface The native surface handle. * \param attribs The attribute list. * \param create_platform If this is true, then the call is from * eglCreatePlatformPixmapSurface. If false, it's from * eglCreatePixmapSurface. * \return The internal EGLSurface handle, or EGL_NO_SURFACE on failure. */ EGLSurface (* CreatePixmapSurface) (EplPlatformData *plat, EplDisplay *pdpy, EplSurface *psurf, EGLConfig config, void *native_surface, const EGLAttrib *attribs, EGLBoolean create_platform); /** * Called to handle eglDestroySurface and eglTerminate. * * Note that it's possible that the EplSurface struct itself might stick around * if another thread is holding a reference to it. * * \c FreeSurface is called when the refcount actually drops to zero. * * \param plat The EplPlatformData struct * \param pdpy The EplDisplay struct */ void (* DestroySurface) (EplDisplay *pdpy, EplSurface *psurf); /** * Called when an EplSurface is about to be freed. * * At this point, it's safe to assume that no other thread is going to touch * the surface, so the platform must free anything that it hasn't already freed * in \c DestroySurface. * * \param plat The EplPlatformData struct * \param pdpy The EplDisplay struct */ void (* FreeSurface) (EplDisplay *pdpy, EplSurface *psurf); /** * Implements eglSwapBuffers and eglSwapBuffersWithDamageEXT. * * If the application calls eglSwapBuffers, then \p rects will be NULL and * \p n_rects will be zero. * * \param plat The EplPlatformData struct * \param pdpy The EplDisplay struct * \param psurf The EplSurface struct. This will always be the thread's * current drawing surface. * \param rects The damage rectangles, or NULL. * \param n_rects The number of damage rectangles. * \return EGL_TRUE on success, EGL_FALSE on failure. */ EGLBoolean (* SwapBuffers) (EplPlatformData *plat, EplDisplay *pdpy, EplSurface *psurf, const EGLint *rects, EGLint n_rects); /** * Implements eglWaitGL and eglWaitClient. * * This function is optional. If it's NULL, then the base library will * not provide a hook function eglWaitGL or eglWaitClient, and so the * driver will follow its default behavior. * * \param pdpy The EplDisplay struct * \param psurf The current EplSurface, or NULL if there isn't a current * surface. * \return EGL_TRUE on success, EGL_FALSE on failure. */ EGLBoolean (*WaitGL) (EplDisplay *pdpy, EplSurface *psurf); /** * Implements eglWaitNative. * * This function is optional. If it's NULL, then the base library will not * provide a hook function eglWaitNative, and so the driver will follow its * default behavior. * * \param pdpy The EplDisplay struct * \param psurf The current EplSurface, or NULL if there isn't a current * surface. * \return EGL_TRUE on success, EGL_FALSE on failure. */ EGLBoolean (*WaitNative) (EplDisplay *pdpy, EplSurface *psurf); } EplImplFuncs; #ifdef __cplusplus } #endif #endif // PLATFORM_IMPL_H egl-x11-1.0.0/src/base/platform-utils.c000066400000000000000000000052041471743126200175250ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: Copyright (c) 2024 NVIDIA CORPORATION & AFFILIATES. All rights reserved. * SPDX-License-Identifier: Apache-2.0 * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "platform-utils.h" #include #include static int HookFuncCmp(const void *key, const void *elem) { return strcmp((const char *) key, ((const EplHookFunc *) elem)->name); } void *eplFindHookFunction(const EplHookFunc *funcs, size_t count, const char *name) { const EplHookFunc *found = bsearch(name, funcs, count, sizeof(EplHookFunc), HookFuncCmp); if (found != NULL) { return found->func; } else { return NULL; } } EGLBoolean eplFindExtension(const char *extension, const char *extensions) { const char *start; const char *where, *terminator; if (extension == NULL || extensions == NULL) { return EGL_FALSE; } start = extensions; for (;;) { where = strstr(start, extension); if (!where) { break; } terminator = where + strlen(extension); if (where == start || *(where - 1) == ' ') { if (*terminator == ' ' || *terminator == '\0') { return EGL_TRUE; } } start = terminator; } return EGL_FALSE; } EGLBoolean eplInitRecursiveMutex(pthread_mutex_t *mutex) { pthread_mutexattr_t attr; if (pthread_mutexattr_init(&attr) != 0) { return EGL_FALSE; } pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); if (pthread_mutex_init(mutex, &attr) != 0) { pthread_mutexattr_destroy(&attr); return EGL_FALSE; } pthread_mutexattr_destroy(&attr); return EGL_TRUE; } EGLint eplCountAttribs(const EGLAttrib *attribs) { EGLint count = 0; if (attribs != NULL) { while (attribs[count] != EGL_NONE) { count += 2; } } return count; } EGLint eplCountAttribs32(const EGLint *attribs) { EGLint count = 0; if (attribs != NULL) { while (attribs[count] != EGL_NONE) { count += 2; } } return count; } egl-x11-1.0.0/src/base/platform-utils.h000066400000000000000000000034761471743126200175430ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: Copyright (c) 2024 NVIDIA CORPORATION & AFFILIATES. All rights reserved. * SPDX-License-Identifier: Apache-2.0 * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef PLATFORM_UTILS_H #define PLATFORM_UTILS_H /** * \file * * Other utility functions that don't fit elsewhere. */ #include #include #include #ifdef __cplusplus extern "C" { #endif typedef struct { const char *name; void *func; } EplHookFunc; /** * Looks up a function from an array of EplHookFunc structs. * * This will use a binary search, so \p funcs must be sorted by name. */ void *eplFindHookFunction(const EplHookFunc *funcs, size_t count, const char *name); /** * Returns true if \p extension is listed in \p extensions. */ EGLBoolean eplFindExtension(const char *extension, const char *extensions); /** * Initializes a recursive mutex. */ EGLBoolean eplInitRecursiveMutex(pthread_mutex_t *mutex); /** * Returns the length of an attribute array. * * \param attribs An EGLAttrib array, or NULL. * \return The length of the array, not including the EGL_NONE at the end. This * will always be a multiple of 2. */ EGLint eplCountAttribs(const EGLAttrib *attribs); EGLint eplCountAttribs32(const EGLint *attribs); #ifdef __cplusplus } #endif #endif // PLATFORM_UTILS_H egl-x11-1.0.0/src/base/refcountobj.c000066400000000000000000000024401471743126200170620ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: Copyright (c) 2024 NVIDIA CORPORATION & AFFILIATES. All rights reserved. * SPDX-License-Identifier: Apache-2.0 * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "refcountobj.h" #include #include void eplRefCountInit(EplRefCount *obj) { obj->refcount = 1; } EplRefCount *eplRefCountRef(EplRefCount *obj) { if (obj != NULL) { __sync_add_and_fetch(&obj->refcount, 1); } return obj; } int eplRefCountUnref(EplRefCount *obj) { if (obj != NULL) { unsigned int prev = __sync_fetch_and_sub(&obj->refcount, 1); assert(prev > 0); if (prev == 1) { // If we were using a mutex, then this is where we'd destroy it. return 1; } } return 0; } egl-x11-1.0.0/src/base/refcountobj.h000066400000000000000000000060011471743126200170640ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: Copyright (c) 2024 NVIDIA CORPORATION & AFFILIATES. All rights reserved. * SPDX-License-Identifier: Apache-2.0 * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef REFCOUNTOBJ_H #define REFCOUNTOBJ_H #include /** * \file * * Helper functions for reference-counted structures. */ #ifdef __cplusplus extern "C" { #endif /** * A simple atomic refcount struct. * * This is intended to be embedded in a larger struct. */ typedef struct { unsigned int refcount; } EplRefCount; /** * Initializes an \c EplRefCount struct. * * This will set the refcount to 1, and (if necessary) initialize a mutex. */ void eplRefCountInit(EplRefCount *obj); /** * Increments the refcount for an \c EplRefCount. Does nothing if \p obj is * NULL. * * \param obj The object to reference, or NULL. * \return \p obj */ EplRefCount *eplRefCountRef(EplRefCount *obj); /** * Decrements the refcount of an \c EplRefCount. Does nothing if \p obj is * NULL. * * If the reference count drops to zero, then this function will clean up the * \c EplRefCount struct and return non-zero. * * \param obj The object to unreference. * \return non-zero if the reference count is now zero, and so the object * should be destroyed. */ int eplRefCountUnref(EplRefCount *obj); /** * Declares functions to reference and unreference an EplRefCount-based struct. * * \param type The name of the larger struct. * \param prefix The prefix of the ref and unref functions. "Ref" and "Unref" * will be appended to this. */ #define EPL_REFCOUNT_DECLARE_TYPE_FUNCS(type, prefix) \ type *prefix##Ref(type *obj); \ void prefix##Unref(type *obj); /** * Defines functions to reference and unreference an EplRefCount-based struct. * * \param type The name of the larger struct. * \param prefix The prefix of the ref and unref functions. "Ref" and "Unref" * will be appended to this. * \param member The name of the embedded EplRefCount struct. * \param freefunc A function to destroy an object when its refcount reaches * zero. This function should take a single \p type pointer as a parameter. */ #define EPL_REFCOUNT_DEFINE_TYPE_FUNCS(type, prefix, member, freefunc) \ type *prefix##Ref(type *obj) { \ if (obj != NULL) eplRefCountRef(&obj->member); \ return obj; \ } \ void prefix##Unref(type *obj) { \ if (obj != NULL && eplRefCountUnref(&obj->member)) { \ freefunc(obj); \ } \ } #ifdef __cplusplus } #endif #endif // REFCOUNTOBJ_H egl-x11-1.0.0/src/x11/000077500000000000000000000000001471743126200140755ustar00rootroot00000000000000egl-x11-1.0.0/src/x11/20_nvidia_xcb.json000066400000000000000000000001571471743126200174020ustar00rootroot00000000000000{ "file_format_version" : "1.0.0", "ICD" : { "library_path" : "libnvidia-egl-xcb.so.1" } } egl-x11-1.0.0/src/x11/20_nvidia_xlib.json000066400000000000000000000001601471743126200175560ustar00rootroot00000000000000{ "file_format_version" : "1.0.0", "ICD" : { "library_path" : "libnvidia-egl-xlib.so.1" } } egl-x11-1.0.0/src/x11/dma-buf.h000066400000000000000000000167021471743126200155670ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ /* * Framework for buffer objects that can be shared across devices/subsystems. * * Copyright(C) 2015 Intel Ltd * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 as published by * the Free Software Foundation. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along with * this program. If not, see . */ #ifndef _DMA_BUF_UAPI_H_ #define _DMA_BUF_UAPI_H_ #if defined(__linux__) #include #else /* One of the BSDs */ #include #include typedef int8_t __s8; typedef uint8_t __u8; typedef int16_t __s16; typedef uint16_t __u16; typedef int32_t __s32; typedef uint32_t __u32; typedef int64_t __s64; typedef uint64_t __u64; #endif /** * struct dma_buf_sync - Synchronize with CPU access. * * When a DMA buffer is accessed from the CPU via mmap, it is not always * possible to guarantee coherency between the CPU-visible map and underlying * memory. To manage coherency, DMA_BUF_IOCTL_SYNC must be used to bracket * any CPU access to give the kernel the chance to shuffle memory around if * needed. * * Prior to accessing the map, the client must call DMA_BUF_IOCTL_SYNC * with DMA_BUF_SYNC_START and the appropriate read/write flags. Once the * access is complete, the client should call DMA_BUF_IOCTL_SYNC with * DMA_BUF_SYNC_END and the same read/write flags. * * The synchronization provided via DMA_BUF_IOCTL_SYNC only provides cache * coherency. It does not prevent other processes or devices from * accessing the memory at the same time. If synchronization with a GPU or * other device driver is required, it is the client's responsibility to * wait for buffer to be ready for reading or writing before calling this * ioctl with DMA_BUF_SYNC_START. Likewise, the client must ensure that * follow-up work is not submitted to GPU or other device driver until * after this ioctl has been called with DMA_BUF_SYNC_END? * * If the driver or API with which the client is interacting uses implicit * synchronization, waiting for prior work to complete can be done via * poll() on the DMA buffer file descriptor. If the driver or API requires * explicit synchronization, the client may have to wait on a sync_file or * other synchronization primitive outside the scope of the DMA buffer API. */ struct dma_buf_sync { /** * @flags: Set of access flags * * DMA_BUF_SYNC_START: * Indicates the start of a map access session. * * DMA_BUF_SYNC_END: * Indicates the end of a map access session. * * DMA_BUF_SYNC_READ: * Indicates that the mapped DMA buffer will be read by the * client via the CPU map. * * DMA_BUF_SYNC_WRITE: * Indicates that the mapped DMA buffer will be written by the * client via the CPU map. * * DMA_BUF_SYNC_RW: * An alias for DMA_BUF_SYNC_READ | DMA_BUF_SYNC_WRITE. */ __u64 flags; }; #define DMA_BUF_SYNC_READ (1 << 0) #define DMA_BUF_SYNC_WRITE (2 << 0) #define DMA_BUF_SYNC_RW (DMA_BUF_SYNC_READ | DMA_BUF_SYNC_WRITE) #define DMA_BUF_SYNC_START (0 << 2) #define DMA_BUF_SYNC_END (1 << 2) #define DMA_BUF_SYNC_VALID_FLAGS_MASK \ (DMA_BUF_SYNC_RW | DMA_BUF_SYNC_END) #define DMA_BUF_NAME_LEN 32 /** * struct dma_buf_export_sync_file - Get a sync_file from a dma-buf * * Userspace can perform a DMA_BUF_IOCTL_EXPORT_SYNC_FILE to retrieve the * current set of fences on a dma-buf file descriptor as a sync_file. CPU * waits via poll() or other driver-specific mechanisms typically wait on * whatever fences are on the dma-buf at the time the wait begins. This * is similar except that it takes a snapshot of the current fences on the * dma-buf for waiting later instead of waiting immediately. This is * useful for modern graphics APIs such as Vulkan which assume an explicit * synchronization model but still need to inter-operate with dma-buf. * * The intended usage pattern is the following: * * 1. Export a sync_file with flags corresponding to the expected GPU usage * via DMA_BUF_IOCTL_EXPORT_SYNC_FILE. * * 2. Submit rendering work which uses the dma-buf. The work should wait on * the exported sync file before rendering and produce another sync_file * when complete. * * 3. Import the rendering-complete sync_file into the dma-buf with flags * corresponding to the GPU usage via DMA_BUF_IOCTL_IMPORT_SYNC_FILE. * * Unlike doing implicit synchronization via a GPU kernel driver's exec ioctl, * the above is not a single atomic operation. If userspace wants to ensure * ordering via these fences, it is the respnosibility of userspace to use * locks or other mechanisms to ensure that no other context adds fences or * submits work between steps 1 and 3 above. */ struct dma_buf_export_sync_file { /** * @flags: Read/write flags * * Must be DMA_BUF_SYNC_READ, DMA_BUF_SYNC_WRITE, or both. * * If DMA_BUF_SYNC_READ is set and DMA_BUF_SYNC_WRITE is not set, * the returned sync file waits on any writers of the dma-buf to * complete. Waiting on the returned sync file is equivalent to * poll() with POLLIN. * * If DMA_BUF_SYNC_WRITE is set, the returned sync file waits on * any users of the dma-buf (read or write) to complete. Waiting * on the returned sync file is equivalent to poll() with POLLOUT. * If both DMA_BUF_SYNC_WRITE and DMA_BUF_SYNC_READ are set, this * is equivalent to just DMA_BUF_SYNC_WRITE. */ __u32 flags; /** @fd: Returned sync file descriptor */ __s32 fd; }; /** * struct dma_buf_import_sync_file - Insert a sync_file into a dma-buf * * Userspace can perform a DMA_BUF_IOCTL_IMPORT_SYNC_FILE to insert a * sync_file into a dma-buf for the purposes of implicit synchronization * with other dma-buf consumers. This allows clients using explicitly * synchronized APIs such as Vulkan to inter-op with dma-buf consumers * which expect implicit synchronization such as OpenGL or most media * drivers/video. */ struct dma_buf_import_sync_file { /** * @flags: Read/write flags * * Must be DMA_BUF_SYNC_READ, DMA_BUF_SYNC_WRITE, or both. * * If DMA_BUF_SYNC_READ is set and DMA_BUF_SYNC_WRITE is not set, * this inserts the sync_file as a read-only fence. Any subsequent * implicitly synchronized writes to this dma-buf will wait on this * fence but reads will not. * * If DMA_BUF_SYNC_WRITE is set, this inserts the sync_file as a * write fence. All subsequent implicitly synchronized access to * this dma-buf will wait on this fence. */ __u32 flags; /** @fd: Sync file descriptor */ __s32 fd; }; #define DMA_BUF_BASE 'b' #define DMA_BUF_IOCTL_SYNC _IOW(DMA_BUF_BASE, 0, struct dma_buf_sync) /* 32/64bitness of this uapi was botched in android, there's no difference * between them in actual uapi, they're just different numbers. */ #define DMA_BUF_SET_NAME _IOW(DMA_BUF_BASE, 1, const char *) #define DMA_BUF_SET_NAME_A _IOW(DMA_BUF_BASE, 1, __u32) #define DMA_BUF_SET_NAME_B _IOW(DMA_BUF_BASE, 1, __u64) #define DMA_BUF_IOCTL_EXPORT_SYNC_FILE _IOWR(DMA_BUF_BASE, 2, struct dma_buf_export_sync_file) #define DMA_BUF_IOCTL_IMPORT_SYNC_FILE _IOW(DMA_BUF_BASE, 3, struct dma_buf_import_sync_file) #endif egl-x11-1.0.0/src/x11/driver-platform-surface.h000066400000000000000000000343101471743126200210120ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: Copyright (c) 2024 NVIDIA CORPORATION & AFFILIATES. All rights reserved. * SPDX-License-Identifier: Apache-2.0 * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /** * \file * * The platform surface interface for the NVIDIA driver. * * This interface provides a new EGLSurface which renders to caller-allocated * color buffers. Conceptually, it's similar to an FBO, but at the EGL level * instead of OpenGL. * * Note that this interface is still somewhat experimental, and might change in * backwards-incompatible ways. */ #ifndef DRIVER_PLATFORM_SURFACE_H #define DRIVER_PLATFORM_SURFACE_H #include #ifdef __cplusplus extern "C" { #endif #define EGL_PLATFORM_SURFACE_UPDATE_CALLBACK_NVX ((EGLAttrib) 0x80000001U) #define EGL_PLATFORM_SURFACE_UPDATE_CALLBACK_PARAM_NVX ((EGLAttrib) 0x80000002U) #define EGL_PLATFORM_SURFACE_DAMAGE_CALLBACK_NVX ((EGLAttrib) 0x80000003U) #define EGL_PLATFORM_SURFACE_DAMAGE_CALLBACK_PARAM_NVX ((EGLAttrib) 0x80000004U) #define EGL_PLATFORM_SURFACE_BLIT_TARGET_NVX ((EGLAttrib) 0x80000005U) /** * If this attribute is EGL_TRUE, then the surface will treat the origin as the * top-left corner (the convention used by e.g., x11). * * If it's EGL_FALSE, then the origin is in the bottom-left corner (the * convention used by OpenGL textures). * * We re-use the same value as EGL_WAYLAND_Y_INVERTED_WL for this, since it's * more-or-less the same meaning. */ #define EGL_SURFACE_Y_INVERTED_NVX 0x31DB #define EGL_PLATFORM_SURFACE_INTERFACE_MAJOR_VERSION 0 #define EGL_PLATFORM_SURFACE_INTERFACE_MINOR_VERSION 1 static inline EGLint EGL_PLATFORM_SURFACE_INTERFACE_GET_MAJOR_VERSION(EGLint version) { return version >> 16; } static inline EGLint EGL_PLATFORM_SURFACE_INTERFACE_GET_MINOR_VERSION(EGLint version) { return version & 0xFFFF; } /** * Checks if the version number reported by the driver is compatible. * * \param driver_version The version number reported by \c eglPlatformGetVersionNVX. * \param major_version The major version number that the library expects. * \param min_minor_version The minimum minor version number that the library requires. * \return EGL_TRUE if the driver's version is compatible. */ static inline EGLBoolean EGL_PLATFORM_SURFACE_INTERFACE_CHECK_VERSION(EGLint driver_version, EGLint major_version, EGLint min_minor_version) { return EGL_PLATFORM_SURFACE_INTERFACE_GET_MAJOR_VERSION(driver_version) == major_version && EGL_PLATFORM_SURFACE_INTERFACE_GET_MINOR_VERSION(driver_version) >= min_minor_version; } /** * An opaque handle to a color buffer. * * Note that a color buffer may only be attached to one attachment point of one * EGLSurface at a time. * * Detaching a buffer from one surface and then attaching it to another is * allowed, but may incur a performance cost (e.g., due to reallocating * multisample buffers). */ typedef struct EGLPlatformColorBufferNVXRec *EGLPlatformColorBufferNVX; /** * A callback to update an EGLSurface. This is used for dealing with window * resizes and similar. * * Because this is called from within the driver, it's not safe to call any EGL * or OpenGL functions from this callback. The callback may only call functions * that are explicitly defined as safe. Calling any other function may result * in undefined behavior. * * This function is only ever called for the current EGLSurface, or from an * eglMakeCurrent call that's setting the EGLSurface to be current. Thus, the * platform library can safely assume that the EGLSurface will not be current * to any other thread, and that there will not be a concurrent call to * eglSwapBuffers for this surface. * * \param param The pointer that was passed as the * \c EGL_PLATFORM_SURFACE_UPDATE_CALLBACK_PARAM_NVX attribute to * \c eglPlatformCreateSurfaceNVX. */ typedef void (* EGLExtPlatformSurfaceUpdateCallback) (void *param); /** * A callback to handle front- or single-buffered rendering for EGL platform * surfaces. * * For a double-buffered EGLSurface, this is called when the front buffer * changed. * * For a single-buffered EGLSurface, this is called when single buffer changed. * * In both cases, it's called after a flush, so whatever rendering happened is * in progress. * * The callback must not call back into the driver at all. Calling any * driver function, including the functions that would be safe from * \c EGLExtPlatformSurfaceUpdateCallback, may result in undefined behavior. * * As with \c EGLExtPlatformSurfaceDamageCallback, this function is only called * for the current thread's current surface. Therefore, the platform library * can assume that there will not be a concurrent call to eglSwapBuffers for * the same EGLSurface on any other thread. * * \note The driver will close \p syncfd after the callback returns. If the * platform library needs to hold onto it, then it must call dup(). * * \param param A pointer to caller-specific data. This is the pointer passed * as the EGL_PLATFORM_SURFACE_DAMAGE_CALLBACK_PARAM_NVX attribute to * \c eglPlatformCreateSurfaceNVX. * \param syncfd A file descriptor for a binary fence, or -1. If this is -1, * then the driver will have already waited for any rendering to finish. * \param flags Currently unused. */ typedef void (* EGLExtPlatformSurfaceDamageCallback) (void *param, int syncfd, unsigned int flags); /** * Returns a version number for the platform surface interface. * * This version contains a major version number in the high-order 16 bits, and * a minor version number in the low-order 16 bits. * * The major version will change if there's ever an interface change that * would break backwards compatibility. * * The minor version number will be incremented for new, backwards-compatible * functions or features. * * Use \c EGL_PLATFORM_SURFACE_INTERFACE_GET_MAJOR_VERSION and * \c EGL_PLATFORM_SURFACE_INTERFACE_GET_MINOR_VERSION to extract the major * and minor version numbers. * * As the interface is not yet stable, neither backward nor forward * compatibility is guaranteed between different versions. */ typedef EGLint (* pfn_eglPlatformGetVersionNVX) (void); /** * Creates an EGLPlatformColorBufferNVX from a dma-buf. * * After this function returns, the caller may close the file descriptor. * * Note that the caller must free the color buffer by calling * \c eglPlatformFreeColorBufferNVX. The driver will not free the color buffers * when the display is terminated. * * This function may be called from the update callback. * * \param dpy The internal EGLDisplay handle. * \param fd The dma-buf file descriptor to import. * \param width The width of the image. * \param height The height of the image. * \param format The fourcc format code. * \param stride The stride or pitch of the image. * \param offset The offset of the image. * \param modifier The format modifier of the image. * * \return A new EGLPlatformColorBufferNVX handle, or NULL on error. */ typedef EGLPlatformColorBufferNVX (* pfn_eglPlatformImportColorBufferNVX) (EGLDisplay dpy, int fd, int width, int height, int format, int stride, int offset, unsigned long long modifier); /** * Allocates a color buffer. * * Note that for a simple renderable buffer, a platform library should * generally allocate the buffer using libgbm and then import it using * \c eglPlatformImportColorBufferNVX, because libgbm allows the driver to * select an optimal format modifier. * * However, this function can be used to allocate a buffer in system memory. * * The caller can use \c eglPlatformExportColorBufferNVX to export the buffer * as a dma-buf. * * This function may be called from the update callback. * * \param dpy The internal EGLDisplay handle. * \param width The width of the image. * \param height The height of the image. * \param format The fourcc format code. * \param modifier The format modifier of the image. * \param force_sysmem If true, then allocate the buffer in sysmem, not in * vidmem. */ typedef EGLPlatformColorBufferNVX (* pfn_eglPlatformAllocColorBufferNVX) (EGLDisplay dpy, int width, int height, int format, unsigned long long modifier, EGLBoolean force_sysmem); /** * Exports a color buffer as a dma-buf. * * This function returns a dma-buf file descriptor for a color buffer, along * with the necessary parameters to import it elsewhere. * * Any of the output parameters may be NULL if the caller does not require * them. * * \param dpy The internal EGLDisplay handle. * \param buffer The color buffer to export. * \param[out] ret_fd Returns the dma-buf file descriptor. The caller * is responsible for closing it. * \param[out] ret_width Returns the width of the image. * \param[out] ret_height Returns the height of the image. * \param[out] ret_format Returns the fourcc format code of the image. * \param[out] ret_stride Returns the stride of the image. * \param[out] ret_offset Returns the offset of the image. * \param[out] ret_modifier Returns the format modifier of the image. * * \return EGL_TRUE on success, or EGL_FALSE on error. */ typedef EGLBoolean (* pfn_eglPlatformExportColorBufferNVX) (EGLDisplay dpy, EGLPlatformColorBufferNVX buffer, int *ret_fd, int *ret_width, int *ret_height, int *ret_format, int *ret_stride, int *ret_offset, unsigned long long *ret_modifier); /** * Copies an image between two buffers. * * Currently, this function only supports copying to a pitch linear buffer. * * The copy operation occurs as part of the OpenGL command stream for the * current context, so any rendering that the current context did to \p src * will complete before the copy. The caller can use normal synchronization * functions (glFinish, EGLSync objects, etc) to wait for the copy to finish. * * This function may NOT be called from the update callback. * * \param dpy The internal EGLDisplay handle. The display must be current. * \param src The source buffer. * \param dst The destination buffer. Currently, this must be a pitch linear * image. * \return EGL_TRUE on success, or EGL_FALSE on failure. */ typedef EGLBoolean (* pfn_eglPlatformCopyColorBufferNVX) (EGLDisplay dpy, EGLPlatformColorBufferNVX src, EGLPlatformColorBufferNVX dst); /** * Frees a color buffer. * * If \p buffer is attached to an EGLSurface, then it will be freed when it is * detached, or when the EGLSurface is destroyed. * * This function may be called from the update callback. * * \param dpy The internal EGLDisplay handle. * \param buffer The EGLPlatformColorBufferNVX to free. */ typedef void (* pfn_eglPlatformFreeColorBufferNVX) (EGLDisplay dpy, EGLPlatformColorBufferNVX buffer); /** * Creates a new platform surface. * * The color buffers are specified in the \p platformAttribs array, using * GL_FRONT and GL_BACK for the front and back buffer. * * The update and damage callbacks can be specified in the \p platformAttribs * as well. * * The \p platformAttribs array is a separate EGLAttrib array so that there * isn't any risk of conflict with existing or future EGL enums. * * The driver accepts the same set of EGLConfigs for platform surfaces that it * does for streams, so \p config must have EGL_STREAM_BIT_KHR set. * * This function may NOT be called from the update callback. * * \param dpy The internal EGLDisplay handle. * \param config The EGLConfig to use for the new surface * \param platformAttribs The color buffers and other platform library attributes. * \param attribs An array of other attributes. * * \return A new EGLSurface, or EGL_NO_SURFACE on error. */ typedef EGLSurface (* pfn_eglPlatformCreateSurfaceNVX) (EGLDisplay dpy, EGLConfig config, const EGLAttrib *platformAttribs, const EGLAttrib *attribs); /** * Sets the color buffers for an EGLSurface. * * This function may be called from the update callback, but only for the * surface that's being updated. * * The buffers are specified in an EGLAttrib array, using the same attributes * as eglPlatformCreateSurfaceNVX. * * Outside the update callback, this function may only be called for the * current thread's current surface (e.g., from eglSwapBuffers). If the surface * is not current, then the result is undefined. * * The EGLSurface must remain single- or double-buffered. For example, if the * EGLSurface was created with only a back buffer, then the platform library * may not attach a front buffer using eglPlatformSetColorBuffersNVX. * * The platform library may, however, change the EGL_PLATFORM_SURFACE_BLIT_TARGET_NVX * attachment between NULL and non-NULL. * * This function may be called from the update callback. * * \param dpy The internal EGLDisplay handle. * \param surf The EGLSurface handle. * \param buffers The new buffers, terminated by EGL_NONE. * * \return EGL_TRUE on success, or EGL_FALSE on failure. */ typedef EGLBoolean (* pfn_eglPlatformSetColorBuffersNVX) (EGLDisplay dpy, EGLSurface surf, const EGLAttrib *buffers); /** * Returns EGLConfig attributes. * * This function is equivalent to eglGetConfigAttrib, but with additional * attributes. * * Currently, this function supports \c EGL_LINUX_DRM_FOURCC_EXT, which returns * a fourcc format code for the EGLConfig, or DRM_FORMAT_INVALID if the config * doesn't have a corresponding fourcc code. * * \param dpy The internal EGLDisplay handle. * \param config The EGLConfig to query. * \param attribute The attribute to look up, which may be * EGL_LINUX_DRM_FOURCC_EXT or any attribute that eglGetConfigAttrib * supports. * \param[out] value Returns the attribute value. * * \return EGL_TRUE on success, or EGL_FALSE on error. */ typedef EGLBoolean (* pfn_eglPlatformGetConfigAttribNVX) (EGLDisplay dpy, EGLConfig config, EGLint attribute, EGLint *value); #ifdef __cplusplus } #endif #endif // DRIVER_PLATFORM_SURFACE_H egl-x11-1.0.0/src/x11/meson.build000066400000000000000000000045701471743126200162450ustar00rootroot00000000000000# SPDX-FileCopyrightText: Copyright (c) 2024 NVIDIA CORPORATION & AFFILIATES. All rights reserved. # SPDX-License-Identifier: Apache-2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. dep_gbm = dependency('gbm') dep_x11 = dependency('x11', required: get_option('xlib')) dep_x11_xcb = dependency('x11-xcb', required: get_option('xlib')) dep_xcb = dependency('xcb') dep_xcb_present = dependency('xcb-present') dep_xcb_dri3 = dependency('xcb-dri3') dep_dl = meson.get_compiler('c').find_library('dl', required : false) enable_xlib = (get_option('xlib').allowed() and dep_x11.found() and dep_x11_xcb.found()) x11_deps = [ dep_libdrm, dep_threads, dep_gbm, dep_xcb, dep_xcb_present, dep_xcb_dri3, dep_dl, ] x11_common_source = [ 'x11-platform.c', 'x11-config.c', 'x11-window.c', 'x11-pixmap.c', 'x11-timeline.c', ] if get_option('xcb') xcb_platform = shared_library('nvidia-egl-xcb', [ x11_common_source, 'x11-platform-xcb.c' ], include_directories: [ inc_base ], c_args : ['-D_GNU_SOURCE'], dependencies: [ x11_deps, dep_eglexternal, ], link_with: [ platform_base ], version : meson.project_version(), gnu_symbol_visibility: 'hidden', install: true) install_data('20_nvidia_xcb.json', install_dir: '@0@/egl/egl_external_platform.d'.format(get_option('datadir'))) endif if enable_xlib xlib_platform = shared_library('nvidia-egl-xlib', [ x11_common_source, 'x11-platform-xlib.c', ], include_directories: [ inc_base ], c_args : ['-D_GNU_SOURCE'], dependencies: [ x11_deps, dep_x11, dep_x11_xcb, dep_eglexternal, ], link_with: [ platform_base ], version : meson.project_version(), gnu_symbol_visibility: 'hidden', install: true) install_data('20_nvidia_xlib.json', install_dir: '@0@/egl/egl_external_platform.d'.format(get_option('datadir'))) endif egl-x11-1.0.0/src/x11/x11-config.c000066400000000000000000000326311471743126200161220ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: Copyright (c) 2024 NVIDIA CORPORATION & AFFILIATES. All rights reserved. * SPDX-License-Identifier: Apache-2.0 * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /** * \file * * Stuff for handling EGLConfigs and formats. */ #include #include #include #include #include #include "x11-platform.h" #include "config-list.h" static int CompareFormatSupportInfo(const void *p1, const void *p2) { uint32_t v1 = *((const uint32_t *) p1); uint32_t v2 = *((const uint32_t *) p2); if (v1 < v2) { return -1; } else if (v1 > v2) { return 1; } else { return 0; } } static EGLBoolean InitDriverFormatModifiers(EplPlatformData *plat, EGLDisplay internal_display, uint32_t fourcc, X11DriverFormat *support) { const EplFormatInfo *fmt = eplFormatInfoLookup(fourcc); EGLuint64KHR *modifiers = NULL; EGLBoolean *external = NULL; EGLint num = 0; EGLint i; if (fmt == NULL) { return EGL_FALSE; } if (!plat->priv->egl.QueryDmaBufModifiersEXT(internal_display, fmt->fourcc, 0, NULL, NULL, &num) || num <= 0) { return EGL_FALSE; } modifiers = malloc(num * (sizeof(EGLuint64KHR) + sizeof(EGLBoolean))); if (modifiers == EGL_FALSE) { return EGL_FALSE; } external = (EGLBoolean *) (modifiers + num); if (!plat->priv->egl.QueryDmaBufModifiersEXT(internal_display, fmt->fourcc, num, modifiers, external, &num) || num <= 0) { num = 0; } support->fourcc = fmt->fourcc; support->fmt = fmt; support->num_modifiers = 0; support->external_modifiers = NULL; support->num_external_modifiers = 0; support->modifiers = malloc(num * sizeof(uint64_t)); if (support->modifiers == NULL) { free(modifiers); return EGL_FALSE; } // Split the modifiers into renderable and external-only. for (i=0; imodifiers[support->num_modifiers++] = modifiers[i]; } } support->external_modifiers = support->modifiers + support->num_modifiers; for (i=0; iexternal_modifiers[support->num_external_modifiers++] = modifiers[i]; } } free(modifiers); if (support->num_modifiers == 0) { free(support->modifiers); support->modifiers = NULL; return EGL_FALSE; } return EGL_TRUE; } EGLBoolean eplX11InitDriverFormats(EplPlatformData *plat, X11DisplayInstance *inst) { EGLint *formats = NULL; EGLint num = 0; EGLint i; if (!plat->priv->egl.QueryDmaBufFormatsEXT(inst->internal_display->edpy, 0, NULL, &num) || num <= 0) { return EGL_FALSE; } formats = malloc(num * sizeof(EGLint)); if (formats == NULL) { return EGL_FALSE; } if (!plat->priv->egl.QueryDmaBufFormatsEXT(inst->internal_display->edpy, num, formats, &num) || num <= 0) { free(formats); return EGL_FALSE; } inst->driver_formats = calloc(1, num * sizeof(X11DriverFormat)); if (inst->driver_formats == NULL) { free(formats); return EGL_FALSE; } inst->num_driver_formats = 0; for (i=0; iinternal_display->edpy, formats[i], &inst->driver_formats[inst->num_driver_formats])) { inst->num_driver_formats++; } } free(formats); if (inst->num_driver_formats == 0) { free(inst->driver_formats); inst->driver_formats = NULL; return EGL_FALSE; } // Sort the list by fourcc code. qsort(inst->driver_formats, inst->num_driver_formats, sizeof(X11DriverFormat), CompareFormatSupportInfo); return EGL_TRUE; } void eplX11CleanupDriverFormats(X11DisplayInstance *inst) { if (inst->driver_formats != NULL) { int i; for (i=0; inum_driver_formats; i++) { free(inst->driver_formats[i].modifiers); } free(inst->driver_formats); inst->driver_formats = NULL; } inst->num_driver_formats = 0; } X11DriverFormat *eplX11FindDriverFormat(X11DisplayInstance *inst, uint32_t fourcc) { return bsearch(&fourcc, inst->driver_formats, inst->num_driver_formats, sizeof(X11DriverFormat), CompareFormatSupportInfo); } static xcb_visualid_t FindVisualForFormat(EplPlatformData *plat, xcb_connection_t *conn, xcb_screen_t *xscreen, const EplFormatInfo *fmt) { xcb_depth_iterator_t depthIter; int depth = fmt->colors[0] + fmt->colors[1] + fmt->colors[2] + fmt->colors[3]; uint32_t red_mask = ((1 << fmt->colors[0]) - 1) << fmt->offset[0]; uint32_t green_mask = ((1 << fmt->colors[1]) - 1) << fmt->offset[1]; uint32_t blue_mask = ((1 << fmt->colors[2]) - 1) << fmt->offset[2]; for (depthIter = xcb_screen_allowed_depths_iterator(xscreen); depthIter.rem > 0; xcb_depth_next(&depthIter)) { if (depthIter.data->depth == depth) { xcb_visualtype_iterator_t visIter = xcb_depth_visuals_iterator(depthIter.data); for (visIter = xcb_depth_visuals_iterator(depthIter.data); visIter.rem > 0; xcb_visualtype_next(&visIter)) { if (visIter.data->_class == XCB_VISUAL_CLASS_TRUE_COLOR && visIter.data->red_mask == red_mask && visIter.data->green_mask == green_mask && visIter.data->blue_mask == blue_mask) { return visIter.data->visual_id; } } } } return 0; } static void SetupConfig(EplPlatformData *plat, X11DisplayInstance *inst, EplConfig *config) { X11DriverFormat *support = NULL; EGLint fourcc = DRM_FORMAT_INVALID; xcb_visualid_t visual; config->surfaceMask &= ~(EGL_WINDOW_BIT | EGL_PIXMAP_BIT); // Query the fourcc code from the driver. if (plat->priv->egl.PlatformGetConfigAttribNVX(inst->internal_display->edpy, config->config, EGL_LINUX_DRM_FOURCC_EXT, &fourcc)) { config->fourcc = (uint32_t) fourcc; } else { config->fourcc = DRM_FORMAT_INVALID; } if (config->fourcc == DRM_FORMAT_INVALID) { // Without a format, we can't do anything with this config. return; } support = eplX11FindDriverFormat(inst, fourcc); if (support == NULL) { // The driver doesn't support importing a dma-buf with this format. return; } // We should be able to support pixmaps with any supported format, as long // as they have a supported modifier. config->surfaceMask |= EGL_PIXMAP_BIT; visual = FindVisualForFormat(inst->platform, inst->conn, inst->xscreen, support->fmt); if (visual != 0) { config->nativeVisualID = visual; config->nativeVisualType = XCB_VISUAL_CLASS_TRUE_COLOR; config->surfaceMask |= EGL_WINDOW_BIT; } else { config->nativeVisualType = EGL_NONE; } } EGLBoolean eplX11InitConfigList(EplPlatformData *plat, X11DisplayInstance *inst) { int i; inst->configs = eplConfigListCreate(plat, inst->internal_display->edpy); if (inst->configs == NULL) { eplSetError(plat, EGL_BAD_ALLOC, "Can't find any usable EGLConfigs"); return EGL_FALSE; } for (i=0; iconfigs->num_configs; i++) { SetupConfig(plat, inst, &inst->configs->configs[i]); } return EGL_TRUE; } static EGLBoolean FilterNativePixmap(EplDisplay *pdpy, EplConfig **configs, EGLint *count, xcb_pixmap_t xpix) { xcb_generic_error_t *error = NULL; xcb_get_geometry_cookie_t geomCookie = xcb_get_geometry(pdpy->priv->inst->conn, xpix); xcb_get_geometry_reply_t *geom = xcb_get_geometry_reply(pdpy->priv->inst->conn, geomCookie, &error); xcb_dri3_buffers_from_pixmap_cookie_t buffersCookie; xcb_dri3_buffers_from_pixmap_reply_t *buffers = NULL; int32_t *fds = NULL; EGLint match = 0; EGLint i; if (geom == NULL) { eplSetError(pdpy->platform, EGL_BAD_NATIVE_PIXMAP, "Invalid native pixmap 0x%x", xpix); free(error); return EGL_FALSE; } if (geom->root != pdpy->priv->inst->xscreen->root) { // TODO: Should this be an error, or should it just return zero matching configs? eplSetError(pdpy->platform, EGL_BAD_NATIVE_PIXMAP, "Pixmap 0x%x is on a different screen", xpix); free(geom); return EGL_FALSE; } // Filter out any configs that have the wrong depth. match = 0; for (i=0; i < *count; i++) { EplConfig *config = configs[i]; const X11DriverFormat *fmt; if (!(config->surfaceMask & EGL_PIXMAP_BIT)) { continue; } assert(config->fourcc != DRM_FORMAT_INVALID); fmt = eplX11FindDriverFormat(pdpy->priv->inst, config->fourcc); if (fmt == NULL) { // If the driver doesn't support this format, then we should never // have set EGL_PIXMAP_BIT on it. assert(!"Can't happen -- no driver support for format"); continue; } if (eplFormatInfoDepth(fmt->fmt) != geom->depth) { continue; } configs[match++] = config; } free(geom); *count = match; if (match == 0) { return EGL_TRUE; } // To check the BPP and format modifiers, we have to use a // DRI3BuffersFromPixmap request. buffersCookie = xcb_dri3_buffers_from_pixmap(pdpy->priv->inst->conn, xpix); buffers = xcb_dri3_buffers_from_pixmap_reply(pdpy->priv->inst->conn, buffersCookie, &error); if (buffers == NULL) { eplSetError(pdpy->platform, EGL_BAD_NATIVE_PIXMAP, "Can't look up dma-buf for pixmap 0x%x\n", xpix); free(error); return EGL_FALSE; } // We don't need the file descriptors, so close them now before we do // anything else. fds = xcb_dri3_buffers_from_pixmap_buffers(buffers); for (i=0; ipriv->inst, config->fourcc); if (fmt->fmt->bpp != buffers->bpp) { continue; } // With PRIME, we can support any pixmap in the server by allocating a // linear pixmap and then sending a CopyArea request. Without PRIME, // we're limited to whatever we can render to directly. if (!pdpy->priv->inst->supports_prime) { EGLint j; EGLBoolean supported = EGL_FALSE; for (j=0; jnum_modifiers; j++) { if (fmt->modifiers[j] == buffers->modifier) { supported = EGL_TRUE; break; } } if (!supported) { continue; } } configs[match++] = config; } *count = match; free(buffers); return EGL_TRUE; } EGLBoolean eplX11HookChooseConfig(EGLDisplay edpy, EGLint const *attribs, EGLConfig *configs, EGLint configSize, EGLint *numConfig) { EplDisplay *pdpy; EGLint matchNativePixmap = XCB_PIXMAP_NONE; EGLBoolean success = EGL_FALSE; EplConfig **found = NULL; EGLint count = 0; pdpy = eplDisplayAcquire(edpy); if (pdpy == NULL) { return EGL_FALSE; } found = eplConfigListChooseConfigs(pdpy->platform, pdpy->internal_display, pdpy->priv->inst->configs, attribs, &count, &matchNativePixmap); if (found == NULL) { goto done; } if (matchNativePixmap != XCB_PIXMAP_NONE) { if (!FilterNativePixmap(pdpy, found, &count, matchNativePixmap)) { goto done; } } success = EGL_TRUE; done: if (success) { eplConfigListReturnConfigs(found, count, configs, configSize, numConfig); } free(found); eplDisplayRelease(pdpy); return success; } EGLBoolean eplX11HookGetConfigAttrib(EGLDisplay edpy, EGLConfig config, EGLint attribute, EGLint *value) { EplDisplay *pdpy = eplDisplayAcquire(edpy); EGLBoolean success = EGL_TRUE; if (pdpy == NULL) { return EGL_FALSE; } success = eplConfigListGetAttribute(pdpy->platform, pdpy->internal_display, pdpy->priv->inst->configs, config, attribute, value); eplDisplayRelease(pdpy); return success; } egl-x11-1.0.0/src/x11/x11-pixmap.c000066400000000000000000000410621471743126200161510ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: Copyright (c) 2024 NVIDIA CORPORATION & AFFILIATES. All rights reserved. * SPDX-License-Identifier: Apache-2.0 * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /** * \file * * Pixmap handling for X11. * * Pixmaps are a lot simpler than windows, since we only have one buffer and we * never need to reallocate it. */ #include "x11-platform.h" #include #include #include #include #include #include #include #include #include #include #include #include #include /** * Data for an X11 pixmap. * * A pixmap is a lot simpler than a window, because there's only one buffer, * and we never have to swap or resize it. So, we just import a dma-buf for the * pixmap when we first create the EGLSurface, and then we don't have to change * anything after that. */ typedef struct { X11DisplayInstance *inst; xcb_pixmap_t xpix; uint32_t width; uint32_t height; EGLPlatformColorBufferNVX buffer; EGLPlatformColorBufferNVX blit_target; int prime_dmabuf; xcb_pixmap_t prime_pixmap; } X11Pixmap; static EGLBoolean CheckDirectSupported(X11DisplayInstance *inst, const X11DriverFormat *fmt, const xcb_dri3_buffers_from_pixmap_reply_t *reply) { int i; if (xcb_dri3_buffers_from_pixmap_buffers_length(reply) != 1) { return EGL_FALSE; } for (i=0; inum_modifiers; i++) { if (fmt->modifiers[i] == reply->modifier) { return EGL_TRUE; } } return EGL_FALSE; } static EGLPlatformColorBufferNVX AllocInternalBuffer(X11DisplayInstance *inst, const X11DriverFormat *fmt, uint32_t width, uint32_t height) { EGLPlatformColorBufferNVX buffer = NULL; struct gbm_bo *gbo = NULL; int fd = -1; gbo = gbm_bo_create_with_modifiers2(inst->gbmdev, width, height, fmt->fourcc, fmt->modifiers, fmt->num_modifiers, 0); if (gbo == NULL) { eplSetError(inst->platform, EGL_BAD_ALLOC, "Failed to allocate internal buffer for PRIME pixmap"); goto done; } fd = gbm_bo_get_fd(gbo); if (fd < 0) { eplSetError(inst->platform, EGL_BAD_ALLOC, "Failed to get internal dma-buf for PRIME pixmap"); goto done; } buffer = inst->platform->priv->egl.PlatformImportColorBufferNVX(inst->internal_display->edpy, fd, width, height, gbm_bo_get_format(gbo), gbm_bo_get_stride(gbo), gbm_bo_get_offset(gbo, 0), gbm_bo_get_modifier(gbo)); if (buffer == NULL) { eplSetError(inst->platform, EGL_BAD_ALLOC, "Failed to import internal dma-buf for PRIME pixmap"); goto done; } done: if (fd >= 0) { close(fd); } if (gbo != NULL) { gbm_bo_destroy(gbo); } return buffer; } static EGLBoolean AllocLinearPixmap(X11DisplayInstance *inst, EplSurface *surf, const X11DriverFormat *fmt, uint32_t width, uint32_t height) { X11Pixmap *ppix = (X11Pixmap *) surf->priv; int stride = 0; int offset = 0; int fd = -1; xcb_void_cookie_t cookie; xcb_generic_error_t *error = NULL; EGLBoolean success = EGL_FALSE; assert(ppix->prime_dmabuf < 0); assert(ppix->blit_target == NULL); ppix->blit_target = inst->platform->priv->egl.PlatformAllocColorBufferNVX(inst->internal_display->edpy, width, height, fmt->fourcc, DRM_FORMAT_MOD_LINEAR, EGL_TRUE); if (ppix->blit_target == NULL) { eplSetError(inst->platform, EGL_BAD_ALLOC, "Failed to allocate internal buffer for linear PRIME pixmap"); goto done; } // Export the image to a dma-buf. if (!inst->platform->priv->egl.PlatformExportColorBufferNVX(inst->internal_display->edpy, ppix->blit_target, &ppix->prime_dmabuf, NULL, NULL, NULL, &stride, &offset, NULL)) { eplSetError(inst->platform, EGL_BAD_ALLOC, "Failed to get internal dma-buf for linear PRIME pixmap"); goto done; } if (ppix->prime_dmabuf < 0) { eplSetError(inst->platform, EGL_BAD_ALLOC, "Possible driver error: Failed to get internal dma-buf for linear PRIME pixmap"); goto done; } // XCB will close the file descriptor after sending the request, so we have // to duplicate it. fd = dup(ppix->prime_dmabuf); if (fd < 0) { eplSetError(inst->platform, EGL_BAD_ALLOC, "Failed to dup dmabuf: %s\n", strerror(errno)); goto done; } ppix->prime_pixmap = xcb_generate_id(inst->conn); cookie = xcb_dri3_pixmap_from_buffers_checked(inst->conn, ppix->prime_pixmap, inst->xscreen->root, 1, width, height, stride, offset, 0, 0, 0, 0, 0, 0, eplFormatInfoDepth(fmt->fmt), fmt->fmt->bpp, DRM_FORMAT_MOD_LINEAR, &fd); error = xcb_request_check(inst->conn, cookie); if (error != NULL) { eplSetError(inst->platform, EGL_BAD_ALLOC, "DRI3PixmapFromBuffers request failed with error %d\n", (int) error->error_code); ppix->prime_pixmap = 0; goto done; } success = EGL_TRUE; done: free(error); return success; } /** * Fetches the dma-buf for a Pixmap from the server and creates an * EGLExtColorBuffer in the driver for it. */ static EGLBoolean ImportPixmap(X11DisplayInstance *inst, EplSurface *surf, xcb_pixmap_t xpix, const EplFormatInfo *fmt, uint32_t width, uint32_t height) { X11Pixmap *ppix = (X11Pixmap *) surf->priv; const X11DriverFormat *driverFmt = eplX11FindDriverFormat(inst, fmt->fourcc); xcb_generic_error_t *error = NULL; xcb_dri3_buffers_from_pixmap_cookie_t cookie; xcb_dri3_buffers_from_pixmap_reply_t *reply = NULL; int depth = fmt->colors[0] + fmt->colors[1] + fmt->colors[2] + fmt->colors[3]; int32_t *fds = NULL; EGLBoolean prime = EGL_FALSE; EGLBoolean success = EGL_FALSE; if (driverFmt == NULL) { // This should never happen: If the format isn't supported, then we // should have caught that when we looked up the EGLConfig. eplSetError(inst->platform, EGL_BAD_ALLOC, "Internal error: Unsupported format 0x%08x\n", fmt->fourcc); goto done; } cookie = xcb_dri3_buffers_from_pixmap(inst->conn, xpix); reply = xcb_dri3_buffers_from_pixmap_reply(inst->conn, cookie, &error); if (reply == NULL) { free(error); goto done; } fds = xcb_dri3_buffers_from_pixmap_buffers(reply); if (reply->depth != depth) { eplSetError(inst->platform, EGL_BAD_MATCH, "Pixmap 0x%x has depth %d, but EGLConfig requires depth %d\n", xpix, reply->depth, depth); goto done; } if (reply->bpp != fmt->bpp) { eplSetError(inst->platform, EGL_BAD_MATCH, "Pixmap 0x%x has bpp %d, but EGLConfig requires bpp %d\n", xpix, reply->bpp, fmt->bpp); goto done; } if (inst->force_prime || !CheckDirectSupported(inst, driverFmt, reply)) { prime = EGL_TRUE; } if (!prime) { ppix->buffer = inst->platform->priv->egl.PlatformImportColorBufferNVX(inst->internal_display->edpy, fds[0], width, height, fmt->fourcc, xcb_dri3_buffers_from_pixmap_strides(reply)[0], xcb_dri3_buffers_from_pixmap_offsets(reply)[0], reply->modifier); if (ppix->buffer == NULL) { eplSetError(inst->platform, EGL_BAD_ALLOC, "Failed to import dma-buf for pixmap"); goto done; } ppix->prime_dmabuf = fds[0]; } else { ppix->buffer = AllocInternalBuffer(inst, driverFmt, width, height); if (ppix->buffer == NULL) { goto done; } if (reply->modifier == DRM_FORMAT_MOD_LINEAR && xcb_dri3_buffers_from_pixmap_buffers_length(reply) == 1) { // We need to use PRIME, but the server is using a linear buffer, so we // can blit to it directly. ppix->blit_target = inst->platform->priv->egl.PlatformImportColorBufferNVX(inst->internal_display->edpy, fds[0], width, height, fmt->fourcc, xcb_dri3_buffers_from_pixmap_strides(reply)[0], xcb_dri3_buffers_from_pixmap_offsets(reply)[0], reply->modifier); if (ppix->blit_target == NULL) { eplSetError(inst->platform, EGL_BAD_ALLOC, "Failed to import dma-buf for pixmap"); goto done; } ppix->prime_dmabuf = fds[0]; } else { // Otherwise, create a linear intermediate pixmap. We'll blit to that // pixmap, and then send a CopyArea request to blit to the caller's // pixmap. if (!AllocLinearPixmap(inst, surf, driverFmt, width, height)) { goto done; } } } success = EGL_TRUE; done: if (reply != NULL) { int i; for (i=0; iprime_dmabuf) { close(fds[i]); } } free(reply); } return success; } static void PixmapDamageCallback(void *param, int syncfd, unsigned int flags) { EplSurface *surf = param; X11Pixmap *ppix = (X11Pixmap *) surf->priv; /* * Note that we should be fine even if another thread is trying to call * eglDestroyPixmap here. * * The driver's eglDestroyPixmap gets called before anything else in the * X11Pixmap struct gets cleaned up. The driver will ensure that any * callbacks have finished and no new callbacks can start when * eglDestroyPixmap returns. */ if (syncfd >= 0) { /* * We don't have any form of explicit sync that we can use for pixmap * rendering, because we're not using PresentPixmap. * * If the server (and the kernel) both support it, then try to use * implicit sync. Otherwise, do a CPU wait so that we can at least get * a consistent functional result in all cases. */ if (ppix->prime_dmabuf < 0 || !eplX11ImportDmaBufSyncFile(ppix->inst, ppix->prime_dmabuf, syncfd)) { eplX11WaitForFD(syncfd); } } if (ppix->prime_pixmap != 0) { // TODO: Should we hold on to the GC that we create here? We should be // able to reuse it for any drawable that has the same depth. xcb_create_gc_value_list_t gcvalues; xcb_gcontext_t gc = xcb_generate_id(ppix->inst->conn); xcb_create_gc_aux(ppix->inst->conn, gc, ppix->xpix, 0, &gcvalues); xcb_copy_area(ppix->inst->conn, ppix->prime_pixmap, ppix->xpix, gc, 0, 0, 0, 0, ppix->width, ppix->height); xcb_free_gc(ppix->inst->conn, gc); } } void eplX11DestroyPixmap(EplSurface *surf) { X11Pixmap *ppix = (X11Pixmap *) surf->priv; surf->priv = NULL; if (ppix != NULL) { if (ppix->inst != NULL) { if (surf->internal_surface != EGL_NO_SURFACE) { ppix->inst->platform->egl.DestroySurface(ppix->inst->internal_display->edpy, surf->internal_surface); } if (ppix->buffer != NULL) { ppix->inst->platform->priv->egl.PlatformFreeColorBufferNVX(ppix->inst->internal_display->edpy, ppix->buffer); } if (ppix->blit_target != NULL) { ppix->inst->platform->priv->egl.PlatformFreeColorBufferNVX(ppix->inst->internal_display->edpy, ppix->blit_target); } if (ppix->prime_pixmap != 0 && ppix->inst->conn != NULL) { xcb_free_pixmap(ppix->inst->conn, ppix->prime_pixmap); } eplX11DisplayInstanceUnref(ppix->inst); } if (ppix->prime_dmabuf >= 0) { close(ppix->prime_dmabuf); } free(ppix); } } static EGLBoolean CheckExistingPixmap(EplDisplay *pdpy, xcb_pixmap_t xpix) { EplSurface *psurf; glvnd_list_for_each_entry(psurf, &pdpy->surface_list, entry) { if (psurf->type == EPL_SURFACE_TYPE_PIXMAP) { X11Pixmap *ppix = (X11Pixmap *) psurf->priv; if (ppix->xpix == xpix) { eplSetError(pdpy->platform, EGL_BAD_ALLOC, "An EGLSurface already exists for pixmap 0x%x\n", xpix); return EGL_FALSE; } } } return EGL_TRUE; } EGLSurface eplX11CreatePixmapSurface(EplPlatformData *plat, EplDisplay *pdpy, EplSurface *surf, EGLConfig config, void *native_surface, const EGLAttrib *attribs, EGLBoolean create_platform) { X11DisplayInstance *inst = pdpy->priv->inst; xcb_pixmap_t xpix = eplX11GetNativeXID(pdpy, native_surface, create_platform); X11Pixmap *ppix = NULL; const EplConfig *configInfo; const EplFormatInfo *fmt; xcb_get_geometry_cookie_t geomCookie; xcb_get_geometry_reply_t *geomReply = NULL; xcb_generic_error_t *error = NULL; EGLSurface esurf = EGL_NO_SURFACE; EGLAttrib buffers[] = { GL_BACK, 0, EGL_PLATFORM_SURFACE_BLIT_TARGET_NVX, 0, EGL_PLATFORM_SURFACE_DAMAGE_CALLBACK_NVX, (EGLAttrib) PixmapDamageCallback, EGL_PLATFORM_SURFACE_DAMAGE_CALLBACK_PARAM_NVX, (EGLAttrib) surf, EGL_NONE }; EGLAttrib *internalAttribs = NULL; if (xpix == 0) { eplSetError(plat, EGL_BAD_NATIVE_PIXMAP, "Invalid native pixmap %p\n", native_surface); return EGL_NO_SURFACE; } if (!CheckExistingPixmap(pdpy, xpix)) { return EGL_NO_SURFACE; } configInfo = eplConfigListFind(inst->configs, config); if (configInfo == NULL) { eplSetError(plat, EGL_BAD_CONFIG, "Invalid EGLConfig %p", config); return EGL_NO_SURFACE; } if (!(configInfo->surfaceMask & EGL_PIXMAP_BIT)) { eplSetError(plat, EGL_BAD_CONFIG, "EGLConfig %p does not support pixmaps", config); return EGL_NO_SURFACE; } fmt = eplFormatInfoLookup(configInfo->fourcc); assert(fmt != NULL); internalAttribs = eplX11GetInternalSurfaceAttribs(plat, pdpy, internalAttribs); if (internalAttribs == NULL) { goto done; } geomCookie = xcb_get_geometry(inst->conn, xpix); geomReply = xcb_get_geometry_reply(inst->conn, geomCookie, &error); if (geomReply == NULL) { eplSetError(plat, EGL_BAD_NATIVE_PIXMAP, "Invalid pixmap 0x%x", xpix); goto done; } if (geomReply->root != inst->xscreen->root) { eplSetError(plat, EGL_BAD_NATIVE_PIXMAP, "Pixmap 0x%x is on the wrong screen", xpix); goto done; } if (geomReply->width <= 0 || geomReply->height <= 0) { eplSetError(plat, EGL_BAD_NATIVE_PIXMAP, "Invalid pixmap size"); goto done; } ppix = calloc(1, sizeof(X11Pixmap)); if (ppix == NULL) { eplSetError(plat, EGL_BAD_ALLOC, "Out of memory"); goto done; } surf->priv = (EplImplSurface *) ppix; ppix->inst = eplX11DisplayInstanceRef(inst); ppix->xpix = xpix; ppix->width = geomReply->width; ppix->height = geomReply->height; ppix->prime_dmabuf = -1; if (!ImportPixmap(inst, surf, xpix, fmt, geomReply->width, geomReply->height)) { goto done; } buffers[1] = (EGLAttrib) ppix->buffer; if (ppix->blit_target != NULL) { buffers[3] = (EGLAttrib) ppix->blit_target; } else { // If we're not using PRIME, then we don't need the damage callback. buffers[2] = EGL_NONE; } esurf = inst->platform->priv->egl.PlatformCreateSurfaceNVX(inst->internal_display->edpy, config, buffers, internalAttribs); if (esurf == EGL_NO_SURFACE) { eplSetError(plat, EGL_BAD_ALLOC, "Failed to allocate EGLSurface"); goto done; } done: if (esurf == EGL_NO_SURFACE) { eplX11DestroyPixmap(surf); } free(geomReply); free(error); free(internalAttribs); return esurf; } egl-x11-1.0.0/src/x11/x11-platform-xcb.c000066400000000000000000000030671471743126200172540ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: Copyright (c) 2024 NVIDIA CORPORATION & AFFILIATES. All rights reserved. * SPDX-License-Identifier: Apache-2.0 * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "x11-platform.h" #include PUBLIC EGLBoolean loadEGLExternalPlatform(int major, int minor, const EGLExtDriver *driver, EGLExtPlatform *extplatform) { return eplX11LoadEGLExternalPlatformCommon(major, minor, driver, extplatform, EGL_PLATFORM_XCB_EXT); } xcb_connection_t *eplX11GetXCBConnection(void *native_display, int *ret_screen) { return NULL; } X11XlibDisplayClosedData *eplX11AddXlibDisplayClosedCallback(void *xlib_native_display) { return NULL; } void eplX11XlibDisplayClosedDataUnref(X11XlibDisplayClosedData *data) { // This should never be called with a non-NULL value, because we never // allocate a X11XlibDisplayClosedData struct. assert(data == NULL); } EGLBoolean eplX11IsNativeClosed(X11XlibDisplayClosedData *data) { return EGL_FALSE; } egl-x11-1.0.0/src/x11/x11-platform-xlib.c000066400000000000000000000137331471743126200174370ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: Copyright (c) 2024 NVIDIA CORPORATION & AFFILIATES. All rights reserved. * SPDX-License-Identifier: Apache-2.0 * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "x11-platform.h" #include #include #include /** * This structure keeps track of a callback that we've registered with * XESetCloseDisplay. * * If the same underlying Display is used with more than one EGLDisplay, then * the EplDisplays will all share the same X11XlibDisplayClosedData struct. */ struct _X11XlibDisplayClosedData { EplRefCount refcount; Display *xdpy; EGLBoolean closed; XExtCodes *ext_codes; struct glvnd_list entry; }; static void CleanupDisplayClosedCallbacks(void) __attribute__((destructor)); static void eplX11DisplayClosedCallbackDataFree(X11XlibDisplayClosedData *callback); static int OnXlibDisplayClosed(Display *xdpy, XExtCodes *codes); /** * A linked list of all DisplayClosedCallbackData structs. * * This has to be a global array, because the callback for XESetCloseDisplay * doesn't have an extra parameter where we could stash an EplDisplay or * EplPlatformData pointer. * * That also means we need a reference count to know when to clean up this * list, in case loadEGLExternalPlatform gets called more than once. */ static struct glvnd_list display_close_callback_list = { &display_close_callback_list, &display_close_callback_list }; static pthread_mutex_t display_close_callback_list_mutex = PTHREAD_MUTEX_INITIALIZER; EPL_REFCOUNT_DEFINE_TYPE_FUNCS(X11XlibDisplayClosedData, eplX11XlibDisplayClosedData, refcount, eplX11DisplayClosedCallbackDataFree); PUBLIC EGLBoolean loadEGLExternalPlatform(int major, int minor, const EGLExtDriver *driver, EGLExtPlatform *extplatform) { return eplX11LoadEGLExternalPlatformCommon(major, minor, driver, extplatform, EGL_PLATFORM_X11_KHR); } xcb_connection_t *eplX11GetXCBConnection(void *native_display, int *ret_screen) { Display *xdpy = native_display; if (ret_screen != NULL) { *ret_screen = DefaultScreen(xdpy); } return XGetXCBConnection(xdpy); } static void RemoveDisplayClosedCallback(X11XlibDisplayClosedData *callback) { glvnd_list_del(&callback->entry); if (callback->ext_codes != NULL) { XESetCloseDisplay(callback->xdpy, callback->ext_codes->extension, NULL); callback->ext_codes = NULL; } eplX11XlibDisplayClosedDataUnref(callback); } static void CleanupDisplayClosedCallbacks(void) { pthread_mutex_lock(&display_close_callback_list_mutex); while (!glvnd_list_is_empty(&display_close_callback_list)) { X11XlibDisplayClosedData *callback = glvnd_list_first_entry(&display_close_callback_list, X11XlibDisplayClosedData, entry); RemoveDisplayClosedCallback(callback); } pthread_mutex_unlock(&display_close_callback_list_mutex); } X11XlibDisplayClosedData *eplX11AddXlibDisplayClosedCallback(void *xlib_native_display) { Display *xdpy = xlib_native_display; X11XlibDisplayClosedData *callback; pthread_mutex_lock(&display_close_callback_list_mutex); glvnd_list_for_each_entry(callback, &display_close_callback_list, entry) { if (callback->xdpy == xdpy) { eplX11XlibDisplayClosedDataRef(callback); pthread_mutex_unlock(&display_close_callback_list_mutex); return callback; } } // We haven't seen this display before, so add a new one. callback = malloc(sizeof(X11XlibDisplayClosedData)); if (callback == NULL) { pthread_mutex_unlock(&display_close_callback_list_mutex); return NULL; } callback->ext_codes = XAddExtension(xdpy); if (callback->ext_codes == NULL) { pthread_mutex_unlock(&display_close_callback_list_mutex); free(callback); return NULL; } eplRefCountInit(&callback->refcount); callback->xdpy = xdpy; callback->closed = EGL_FALSE; XESetCloseDisplay(callback->xdpy, callback->ext_codes->extension, OnXlibDisplayClosed); eplX11XlibDisplayClosedDataRef(callback); glvnd_list_add(&callback->entry, &display_close_callback_list); pthread_mutex_unlock(&display_close_callback_list_mutex); return callback; } static int OnXlibDisplayClosed(Display *xdpy, XExtCodes *codes) { X11XlibDisplayClosedData *callback, *callbackTmp; pthread_mutex_lock(&display_close_callback_list_mutex); glvnd_list_for_each_entry_safe(callback, callbackTmp, &display_close_callback_list, entry) { if (xdpy == callback->xdpy) { assert(codes == callback->ext_codes); assert(!callback->closed); callback->closed = EGL_TRUE; RemoveDisplayClosedCallback(callback); break; } } pthread_mutex_unlock(&display_close_callback_list_mutex); return 0; } static void eplX11DisplayClosedCallbackDataFree(X11XlibDisplayClosedData *callback) { // We should have already unregistered the callback in OnXlibDisplayClosed // or eplX11CleanupPlatform. assert(callback->ext_codes == NULL); free(callback); } EGLBoolean eplX11IsNativeClosed(X11XlibDisplayClosedData *data) { EGLBoolean ret = EGL_FALSE; if (data != NULL) { pthread_mutex_lock(&display_close_callback_list_mutex); ret = data->closed; pthread_mutex_unlock(&display_close_callback_list_mutex); } return ret; } egl-x11-1.0.0/src/x11/x11-platform.c000066400000000000000000001304651471743126200165050ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: Copyright (c) 2024 NVIDIA CORPORATION & AFFILIATES. All rights reserved. * SPDX-License-Identifier: Apache-2.0 * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /** * \file * * Platform and display-handling code for X11. */ #include "x11-platform.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "platform-utils.h" #include "dma-buf.h" static const char *FORCE_ENABLE_ENV = "__NV_FORCE_ENABLE_X11_EGL_PLATFORM"; #define CLIENT_EXTENSIONS_XLIB "EGL_KHR_platform_x11 EGL_EXT_platform_x11" #define CLIENT_EXTENSIONS_XCB "EGL_EXT_platform_xcb" static const EGLint NEED_PLATFORM_SURFACE_MAJOR = 0; static const EGLint NEED_PLATFORM_SURFACE_MINOR = 1; static const uint32_t NEED_DRI3_MAJOR = 1; static const uint32_t NEED_DRI3_MINOR = 2; static const uint32_t REQUEST_DRI3_MINOR = 4; static const uint32_t NEED_PRESENT_MAJOR = 1; static const uint32_t NEED_PRESENT_MINOR = 2; static const uint32_t REQUEST_PRESENT_MINOR = 4; static X11DisplayInstance *eplX11DisplayInstanceCreate(EplDisplay *pdpy, EGLBoolean from_init); static void eplX11DisplayInstanceFree(X11DisplayInstance *inst); EPL_REFCOUNT_DEFINE_TYPE_FUNCS(X11DisplayInstance, eplX11DisplayInstance, refcount, eplX11DisplayInstanceFree); static void eplX11CleanupPlatform(EplPlatformData *plat); static void eplX11CleanupDisplay(EplDisplay *pdpy); static const char *eplX11QueryString(EplPlatformData *plat, EplDisplay *pdpy, EGLExtPlatformString name); static void *eplX11GetHookFunction(EplPlatformData *plat, const char *name); static EGLBoolean eplX11IsSameDisplay(EplPlatformData *plat, EplDisplay *pdpy, EGLint platform, void *native_display, const EGLAttrib *attribs); static EGLBoolean eplX11GetPlatformDisplay(EplPlatformData *plat, EplDisplay *pdpy, void *native_display, const EGLAttrib *attribs, struct glvnd_list *existing_displays); static EGLBoolean eplX11InitializeDisplay(EplPlatformData *plat, EplDisplay *pdpy, EGLint *major, EGLint *minor); static void eplX11TerminateDisplay(EplPlatformData *plat, EplDisplay *pdpy); static void eplX11DestroySurface(EplDisplay *pdpy, EplSurface *surf); static void eplX11FreeSurface(EplDisplay *pdpy, EplSurface *surf); static EGLBoolean eplX11WaitGL(EplDisplay *pdpy, EplSurface *psurf); static const EplHookFunc X11_HOOK_FUNCTIONS[] = { { "eglChooseConfig", eplX11HookChooseConfig }, { "eglGetConfigAttrib", eplX11HookGetConfigAttrib }, { "eglSwapInterval", eplX11SwapInterval }, }; static const int NUM_X11_HOOK_FUNCTIONS = sizeof(X11_HOOK_FUNCTIONS) / sizeof(X11_HOOK_FUNCTIONS[0]); static const EplImplFuncs X11_IMPL_FUNCS = { .CleanupPlatform = eplX11CleanupPlatform, .QueryString = eplX11QueryString, .GetHookFunction = eplX11GetHookFunction, .IsSameDisplay = eplX11IsSameDisplay, .GetPlatformDisplay = eplX11GetPlatformDisplay, .CleanupDisplay = eplX11CleanupDisplay, .InitializeDisplay = eplX11InitializeDisplay, .TerminateDisplay = eplX11TerminateDisplay, .CreateWindowSurface = eplX11CreateWindowSurface, .CreatePixmapSurface = eplX11CreatePixmapSurface, .DestroySurface = eplX11DestroySurface, .FreeSurface = eplX11FreeSurface, .SwapBuffers = eplX11SwapBuffers, .WaitGL = eplX11WaitGL, }; /** * True if the kernel might support DMA_BUF_IOCTL_IMPORT_SYNC_FILE and * DMA_BUF_IOCTL_EXPORT_SYNC_FILE. * * There's no direct way to query that support, so instead, if an ioctl fails, * then we set this flag to false so that we don't waste time trying again. */ static EGLBoolean import_sync_file_supported = EGL_TRUE; static pthread_mutex_t import_sync_file_supported_mutex = PTHREAD_MUTEX_INITIALIZER; static EGLBoolean LoadProcHelper(EplPlatformData *plat, void *handle, void **ptr, const char *name) { *ptr = dlsym(handle, name); if (*ptr == NULL) { return EGL_FALSE; } return EGL_TRUE; } EGLBoolean eplX11LoadEGLExternalPlatformCommon(int major, int minor, const EGLExtDriver *driver, EGLExtPlatform *extplatform, EGLint platform_enum) { EplPlatformData *plat = NULL; EGLBoolean timelineSupported = EGL_TRUE; pfn_eglPlatformGetVersionNVX ptr_eglPlatformGetVersionNVX; // Before we do anything else, make sure that we've got a recent enough // version of libgbm. if (dlsym(RTLD_DEFAULT, "gbm_bo_create_with_modifiers2") == NULL) { return EGL_FALSE; } plat = eplPlatformBaseAllocate(major, minor, driver, extplatform, platform_enum, &X11_IMPL_FUNCS, sizeof(EplImplPlatform)); if (plat == NULL) { return EGL_FALSE; } ptr_eglPlatformGetVersionNVX = driver->getProcAddress("eglPlatformGetVersionNVX"); if (ptr_eglPlatformGetVersionNVX == NULL || !EGL_PLATFORM_SURFACE_INTERFACE_CHECK_VERSION(ptr_eglPlatformGetVersionNVX(), NEED_PLATFORM_SURFACE_MAJOR, NEED_PLATFORM_SURFACE_MINOR)) { // The driver doesn't support a compatible version of the platform // surface interface. eplPlatformBaseInitFail(plat); return EGL_FALSE; } plat->priv->egl.QueryDisplayAttribKHR = driver->getProcAddress("eglQueryDisplayAttribKHR"); plat->priv->egl.SwapInterval = driver->getProcAddress("eglSwapInterval"); plat->priv->egl.QueryDmaBufFormatsEXT = driver->getProcAddress("eglQueryDmaBufFormatsEXT"); plat->priv->egl.QueryDmaBufModifiersEXT = driver->getProcAddress("eglQueryDmaBufModifiersEXT"); plat->priv->egl.CreateSync = driver->getProcAddress("eglCreateSync"); plat->priv->egl.DestroySync = driver->getProcAddress("eglDestroySync"); plat->priv->egl.WaitSync = driver->getProcAddress("eglWaitSync"); plat->priv->egl.DupNativeFenceFDANDROID = driver->getProcAddress("eglDupNativeFenceFDANDROID"); plat->priv->egl.Flush = driver->getProcAddress("glFlush"); plat->priv->egl.Finish = driver->getProcAddress("glFinish"); plat->priv->egl.PlatformImportColorBufferNVX = driver->getProcAddress("eglPlatformImportColorBufferNVX"); plat->priv->egl.PlatformFreeColorBufferNVX = driver->getProcAddress("eglPlatformFreeColorBufferNVX"); plat->priv->egl.PlatformCreateSurfaceNVX = driver->getProcAddress("eglPlatformCreateSurfaceNVX"); plat->priv->egl.PlatformSetColorBuffersNVX = driver->getProcAddress("eglPlatformSetColorBuffersNVX"); plat->priv->egl.PlatformGetConfigAttribNVX = driver->getProcAddress("eglPlatformGetConfigAttribNVX"); plat->priv->egl.PlatformCopyColorBufferNVX = driver->getProcAddress("eglPlatformCopyColorBufferNVX"); plat->priv->egl.PlatformAllocColorBufferNVX = driver->getProcAddress("eglPlatformAllocColorBufferNVX"); plat->priv->egl.PlatformExportColorBufferNVX = driver->getProcAddress("eglPlatformExportColorBufferNVX"); if (plat->priv->egl.QueryDisplayAttribKHR == NULL || plat->priv->egl.SwapInterval == NULL || plat->priv->egl.QueryDmaBufFormatsEXT == NULL || plat->priv->egl.QueryDmaBufModifiersEXT == NULL || plat->priv->egl.CreateSync == NULL || plat->priv->egl.DestroySync == NULL || plat->priv->egl.WaitSync == NULL || plat->priv->egl.DupNativeFenceFDANDROID == NULL || plat->priv->egl.Finish == NULL || plat->priv->egl.Flush == NULL || plat->priv->egl.PlatformImportColorBufferNVX == NULL || plat->priv->egl.PlatformFreeColorBufferNVX == NULL || plat->priv->egl.PlatformCreateSurfaceNVX == NULL || plat->priv->egl.PlatformSetColorBuffersNVX == NULL || plat->priv->egl.PlatformGetConfigAttribNVX == NULL || plat->priv->egl.PlatformCopyColorBufferNVX == NULL || plat->priv->egl.PlatformAllocColorBufferNVX == NULL || plat->priv->egl.PlatformExportColorBufferNVX == NULL) { eplPlatformBaseInitFail(plat); return EGL_FALSE; } #define LOAD_PROC(supported, prefix, group, name) \ supported = supported && LoadProcHelper(plat, RTLD_DEFAULT, (void **) &plat->priv->group.name, prefix #name) // Load the functions that we'll need for explicit sync, if they're // available. If we don't find these, then it's not fatal. LOAD_PROC(timelineSupported, "xcb_", xcb, dri3_import_syncobj); LOAD_PROC(timelineSupported, "xcb_", xcb, dri3_free_syncobj); LOAD_PROC(timelineSupported, "xcb_", xcb, present_pixmap_synced); LOAD_PROC(timelineSupported, "drm", drm, GetCap); LOAD_PROC(timelineSupported, "drm", drm, SyncobjCreate); LOAD_PROC(timelineSupported, "drm", drm, SyncobjDestroy); LOAD_PROC(timelineSupported, "drm", drm, SyncobjHandleToFD); LOAD_PROC(timelineSupported, "drm", drm, SyncobjFDToHandle); LOAD_PROC(timelineSupported, "drm", drm, SyncobjImportSyncFile); LOAD_PROC(timelineSupported, "drm", drm, SyncobjExportSyncFile); LOAD_PROC(timelineSupported, "drm", drm, SyncobjTimelineSignal); LOAD_PROC(timelineSupported, "drm", drm, SyncobjTimelineWait); LOAD_PROC(timelineSupported, "drm", drm, SyncobjTransfer); plat->priv->timeline_funcs_supported = timelineSupported; #undef LOAD_PROC eplPlatformBaseInitFinish(plat); return EGL_TRUE; } static void eplX11CleanupPlatform(EplPlatformData *plat) { // Nothing to do here yet. } static const char *eplX11QueryString(EplPlatformData *plat, EplDisplay *pdpy, EGLExtPlatformString name) { assert(plat != NULL); switch (name) { case EGL_EXT_PLATFORM_PLATFORM_CLIENT_EXTENSIONS: if (plat->platform_enum == EGL_PLATFORM_X11_KHR) { return CLIENT_EXTENSIONS_XLIB; } else if (plat->platform_enum == EGL_PLATFORM_XCB_EXT) { return CLIENT_EXTENSIONS_XCB; } else { assert(!"Can't happen: Invalid platform enum"); return ""; } case EGL_EXT_PLATFORM_DISPLAY_EXTENSIONS: return ""; default: return NULL; } } static void *eplX11GetHookFunction(EplPlatformData *plat, const char *name) { return eplFindHookFunction(X11_HOOK_FUNCTIONS, NUM_X11_HOOK_FUNCTIONS, name); } /** * Parses the attributes for an eglGetPlatformDisplay call. * * This is used to handle creating a new EGLDisplay and finding a matching * EGLDisplay. * * This mainly exists to deal with picking whether to look for * EGL_PLATFORM_X11_SCREEN_KHR or EGL_PLATFORM_XCB_SCREEN_EXT for the screen * number. * * \param plat The EplPlatformData pointer. * \param platform The platform enum, which should be either EGL_PLATFORM_X11_KHR * or EGL_PLATFORM_XCB_EXT. * \param attribs The attribute array passed to eglGetPlatformDisplay. * \param report_errors If true, then report any invalid attributes. * \param[out] ret_screen Returns the screen number, or -1 if it wasn't * specified. * \param[out] ret_device Returns the EGL_DEVICE_EXT attribute, or * EGL_NO_DEVICE if it wasn't specified. * \return EGL_TRUE on success, or EGL_FALSE if the attributes were invalid. */ static EGLBoolean ParseDisplayAttribs(EplPlatformData *plat, EGLint platform, const EGLAttrib *attribs, EGLBoolean report_errors, int *ret_screen, EGLDeviceEXT *ret_device) { EGLint screenAttrib = EGL_NONE; EGLDeviceEXT device = EGL_NO_DEVICE_EXT; int screen = -1; if (platform == EGL_PLATFORM_X11_KHR) { screenAttrib = EGL_PLATFORM_X11_SCREEN_KHR; } else if (platform == EGL_PLATFORM_XCB_EXT) { screenAttrib = EGL_PLATFORM_XCB_SCREEN_EXT; } else { if (report_errors) { // This shouldn't happen: The driver shouldn't have even called // into this library with the wrong platform enum. eplSetError(plat, EGL_BAD_PARAMETER, "Unsupported platform enum 0x%04x", platform); } return EGL_FALSE; } if (attribs != NULL) { int i; for (i=0; attribs[i] != EGL_NONE; i += 2) { if (attribs[i] == screenAttrib) { screen = (int) attribs[i + 1]; if (screen < 0) { if (report_errors) { eplSetError(plat, EGL_BAD_PARAMETER, "Invalid screen number %d", screen); } return EGL_FALSE; } } else if (attribs[i] == EGL_DEVICE_EXT) { device = (EGLDeviceEXT) attribs[i + 1]; } else { if (report_errors) { eplSetError(plat, EGL_BAD_ATTRIBUTE, "Invalid attribute 0x%lx", (unsigned long) attribs[i]); } return EGL_FALSE; } } } if (ret_screen != NULL) { *ret_screen = screen; } if (ret_device != NULL) { *ret_device = device; } return EGL_TRUE; } static EGLBoolean eplX11IsSameDisplay(EplPlatformData *plat, EplDisplay *pdpy, EGLint platform, void *native_display, const EGLAttrib *attribs) { int screen = -1; EGLDeviceEXT device = EGL_NO_DEVICE_EXT; if (eplX11IsNativeClosed(pdpy->priv->closed_callback)) { // This could happen if the application called XCloseDisplay, but then // a subsequent XOpenDisplay call happened to return a Display at the // same address. In that case, we still treat it as a different native // display. return EGL_FALSE; } if (!ParseDisplayAttribs(plat, platform, attribs, EGL_FALSE, &screen, &device)) { return EGL_FALSE; } if (pdpy->priv->screen_attrib != screen) { return EGL_FALSE; } if (pdpy->priv->device_attrib != device) { return EGL_FALSE; } return EGL_TRUE; } /** * Finds an EGLDeviceEXT handle that corresponds to a given DRI device node. */ static EGLDeviceEXT FindDeviceForNode(EplPlatformData *plat, const char *node) { EGLDeviceEXT *devices = NULL; EGLDeviceEXT found = EGL_NO_DEVICE_EXT; EGLint num = 0; int i; if (!plat->egl.QueryDevicesEXT(0, NULL, &num) || num <= 0) { return EGL_NO_DEVICE_EXT; } devices = alloca(num * sizeof(EGLDeviceEXT)); if (!plat->egl.QueryDevicesEXT(num, devices, &num) || num <= 0) { return EGL_NO_DEVICE_EXT; } for (i=0; iegl.QueryDeviceStringEXT(devices[i], EGL_EXTENSIONS); if (!eplFindExtension("EGL_EXT_device_drm", str)) { continue; } str = plat->egl.QueryDeviceStringEXT(devices[i], EGL_DRM_DEVICE_FILE_EXT); if (str != NULL && strcmp(str, node) == 0) { found = devices[i]; break; } } return found; } /** * Returns an EGLDeviceEXT that corresponds to a device node. * * This is used to translate the file descriptor from DRI3Open into an * EGLDeviceEXT. */ static EGLDeviceEXT FindDeviceForFD(EplPlatformData *plat, int fd) { drmDevice *dev = NULL; int ret; EGLDeviceEXT found = EGL_NO_DEVICE_EXT; ret = drmGetDevice(fd, &dev); if (ret != 0) { return EGL_NO_DEVICE_EXT; } if ((dev->available_nodes & (1 << DRM_NODE_PRIMARY)) != 0 && dev->nodes[DRM_NODE_PRIMARY] != NULL) { EGLBoolean isNV = EGL_FALSE; /* * Call into libdrm to figure out whether this is an NVIDIA device * before we call eglQueryDevicesEXT. * * Calling eglQueryDevicesEXT could require waking up the GPU, which * can very slow and wastes battery. */ if (dev->bustype == DRM_BUS_PCI) { // For a PCI device, just look at the PCI vendor ID. isNV = (dev->deviceinfo.pci->vendor_id == 0x10de); } else { // Tegra GPU's are not PCI devices, so for those, we have to check // the driver name instead. drmVersion *version = drmGetVersion(fd); if (version != NULL) { if (version->name != NULL) { if (strcmp(version->name, "nvidia-drm") == 0 || strcmp(version->name, "tegra-udrm") == 0 || strcmp(version->name, "tegra") == 0) { isNV = EGL_TRUE; } } drmFreeVersion(version); } } if (isNV) { found = FindDeviceForNode(plat, dev->nodes[DRM_NODE_PRIMARY]); } } drmFreeDevice(&dev); return found; } /** * Finds the xcb_screen_t for a screen number. */ static xcb_screen_t *GetXCBScreen(xcb_connection_t *conn, int screen) { xcb_screen_iterator_t iter = xcb_setup_roots_iterator(xcb_get_setup(conn)); int i; if (screen < 0 || iter.rem < screen) { return NULL; } for (i=0; iroot, 0); xcb_dri3_open_reply_t *reply = xcb_dri3_open_reply(conn, cookie, &error); int fd; if (reply == NULL) { free(error); return -1; } assert(reply->nfd == 1); fd = xcb_dri3_open_reply_fds(conn, reply)[0]; free(reply); return fd; } static EGLBoolean eplX11GetPlatformDisplay(EplPlatformData *plat, EplDisplay *pdpy, void *native_display, const EGLAttrib *attribs, struct glvnd_list *existing_displays) { const char *env; X11DisplayInstance *inst; env = getenv("DISPLAY"); if (env == NULL && native_display == NULL) { return EGL_FALSE; } pdpy->priv = calloc(1, sizeof(EplImplDisplay)); if (pdpy->priv == NULL) { eplSetError(plat, EGL_BAD_ALLOC, "Out of memory"); return EGL_FALSE; } if (env != NULL) { pdpy->priv->display_env = strdup(env); if (pdpy->priv->display_env == NULL) { eplSetError(plat, EGL_BAD_ALLOC, "Out of memory"); eplX11CleanupDisplay(pdpy); return EGL_FALSE; } } if (!ParseDisplayAttribs(plat, pdpy->platform_enum, attribs, EGL_TRUE, &pdpy->priv->screen_attrib, &pdpy->priv->device_attrib)) { eplX11CleanupDisplay(pdpy); return EGL_FALSE; } env = getenv("__NV_PRIME_RENDER_OFFLOAD_PROVIDER"); if (env != NULL) { pdpy->priv->requested_device = FindDeviceForNode(plat, env); pdpy->priv->enable_alt_device = EGL_TRUE; } else { env = getenv("__NV_PRIME_RENDER_OFFLOAD"); if (env != NULL && atoi(env) != 0) { pdpy->priv->enable_alt_device = EGL_TRUE; } } if (pdpy->priv->requested_device == EGL_NO_DEVICE_EXT) { // If the caller specified a device, then make sure it's valid. if (pdpy->priv->device_attrib != EGL_NO_DEVICE_EXT) { EGLint num = 0; EGLDeviceEXT *devices = eplGetAllDevices(plat, &num); EGLBoolean valid = EGL_FALSE; EGLint i; if (devices == NULL) { eplX11CleanupDisplay(pdpy); return EGL_FALSE; } for (i=0; ipriv->device_attrib) { valid = EGL_TRUE; break; } } free(devices); if (valid) { // The requested device is a valid NVIDIA device, so use it. pdpy->priv->requested_device = pdpy->priv->device_attrib; } else if (pdpy->priv->enable_alt_device) { // The requested device is not an NVIDIA device, but PRIME is // enabled, so we'll pick an NVIDIA device during eglInitialize. pdpy->priv->requested_device = EGL_NO_DEVICE_EXT; } else { // The requested device is not an NVIDIA device and PRIME is not // enabled. Return failure to let another driver handle it. eplSetError(plat, EGL_BAD_MATCH, "Unknown or non-NV device handle %p", pdpy->priv->device_attrib); eplX11CleanupDisplay(pdpy); return EGL_FALSE; } } } /* * Ideally, we'd wait until eglInitialize to open the connection or do the * rest of our compatibility checks, but we have to do that now to check * whether we can actually support whichever server we're connecting to. */ inst = eplX11DisplayInstanceCreate(pdpy, EGL_FALSE); if (inst == NULL) { eplX11CleanupDisplay(pdpy); return EGL_FALSE; } eplX11DisplayInstanceUnref(inst); if (pdpy->platform_enum == EGL_PLATFORM_X11_KHR && native_display != NULL) { // Note that if this fails, then it's not necessarily fatal. We just // won't get a callback when the application calls XCloseDisplay, which // is no worse than with XCB. pdpy->priv->closed_callback = eplX11AddXlibDisplayClosedCallback(native_display); } return EGL_TRUE; } static void eplX11CleanupDisplay(EplDisplay *pdpy) { if (pdpy->priv != NULL) { eplX11DisplayInstanceUnref(pdpy->priv->inst); eplX11XlibDisplayClosedDataUnref(pdpy->priv->closed_callback); free(pdpy->priv->display_env); free(pdpy->priv); pdpy->priv = NULL; } } /** * Checks whether the server has the necessary support that we need. * * This checks that we're using a domain socket, and checks the versions of the * DRI3 and Present extensions. */ static EGLBoolean CheckServerExtensions(X11DisplayInstance *inst) { const char *env; struct sockaddr addr; socklen_t addrlen = sizeof(addr); const xcb_query_extension_reply_t *extReply; xcb_generic_error_t *error = NULL; xcb_dri3_query_version_cookie_t dri3Cookie; xcb_dri3_query_version_reply_t *dri3Reply = NULL; xcb_present_query_version_cookie_t presentCookie; xcb_present_query_version_reply_t *presentReply = NULL; xcb_query_extension_reply_t *nvglxReply = NULL; EGLBoolean success = EGL_FALSE; // Check to make sure that we're using a domain socket, since we need to be // able to push file descriptors through it. if (getsockname(xcb_get_file_descriptor(inst->conn), &addr, &addrlen) != 0) { return EGL_FALSE; } if (addr.sa_family != AF_UNIX) { return EGL_FALSE; } extReply = xcb_get_extension_data(inst->conn, &xcb_dri3_id); if (extReply == NULL || !extReply->present) { return EGL_FALSE; } extReply = xcb_get_extension_data(inst->conn, &xcb_present_id); if (extReply == NULL || !extReply->present) { return EGL_FALSE; } env = getenv(FORCE_ENABLE_ENV); if (env == NULL || atoi(env) == 0) { /* * Check if the NV-GLX extension is present. If it is, then that means * we're talking to a normal X server running with the NVIDIA driver. * In that case, fail here so that the driver can use its normal X11 * path. * * Note that if/when this replaces our existing X11 path for EGL, then * we could add some requests to NV-GLX to support older (pre DRI3 1.2) * servers or non-Linux systems. */ const char NVGLX_EXTENSION_NAME[] = "NV-GLX"; xcb_query_extension_cookie_t extCookie = xcb_query_extension(inst->conn, sizeof(NVGLX_EXTENSION_NAME) - 1, NVGLX_EXTENSION_NAME); nvglxReply = xcb_query_extension_reply(inst->conn, extCookie, &error); if (nvglxReply == NULL) { // XQueryExtension isn't supposed to generate any errors. goto done; } if (nvglxReply->present) { goto done; } } // TODO: Send these requests in parallel, not in sequence dri3Cookie = xcb_dri3_query_version(inst->conn, NEED_DRI3_MAJOR, REQUEST_DRI3_MINOR); dri3Reply = xcb_dri3_query_version_reply(inst->conn, dri3Cookie, &error); if (dri3Reply == NULL) { goto done; } if (dri3Reply->major_version != NEED_DRI3_MAJOR || dri3Reply->minor_version < NEED_DRI3_MINOR) { goto done; } presentCookie = xcb_present_query_version(inst->conn, NEED_PRESENT_MAJOR, REQUEST_PRESENT_MINOR); presentReply = xcb_present_query_version_reply(inst->conn, presentCookie, &error); if (presentReply == NULL) { goto done; } if (presentReply->major_version != NEED_PRESENT_MAJOR || presentReply->minor_version < NEED_PRESENT_MINOR) { goto done; } if (inst->platform->priv->timeline_funcs_supported && dri3Reply->minor_version >= 4 && presentReply->minor_version >= 4) { /* * The server supports the necessary extension versions, and we've got * the necessary driver and library support in the client. Note that * we still have to send a PresentQueryCapabilities request in * eplX11CreateWindowSurface to check whether the server actually * supports timeline objects. */ inst->supports_explicit_sync = EGL_TRUE; } success = EGL_TRUE; done: free(nvglxReply); free(presentReply); free(dri3Reply); free(error); return success; } static EGLBoolean CheckServerFormatSupport(X11DisplayInstance *inst, EGLBoolean *ret_supports_direct, EGLBoolean *ret_supports_linear) { const X11DriverFormat *fmt = NULL; xcb_dri3_get_supported_modifiers_cookie_t cookie; xcb_dri3_get_supported_modifiers_reply_t *reply = NULL; xcb_generic_error_t *error = NULL; int numScreenMods; const uint64_t *screenMods = NULL; int i, j; EGLBoolean found = EGL_FALSE; // Use XRGB8 to check for server support. With our driver, every format // should have the same set of modifiers, so we just need to pick something // that we'll always support. fmt = eplX11FindDriverFormat(inst, DRM_FORMAT_XRGB8888); cookie = xcb_dri3_get_supported_modifiers(inst->conn, inst->xscreen->root, eplFormatInfoDepth(fmt->fmt), fmt->fmt->bpp); reply = xcb_dri3_get_supported_modifiers_reply(inst->conn, cookie, &error); if (reply == NULL) { free(error); return EGL_FALSE; } numScreenMods = xcb_dri3_get_supported_modifiers_screen_modifiers_length(reply); screenMods = xcb_dri3_get_supported_modifiers_screen_modifiers(reply); *ret_supports_linear = EGL_FALSE; for (i=0; inum_modifiers && !found; j++) { if (screenMods[i] == fmt->modifiers[j]) { found = EGL_TRUE; } } } *ret_supports_direct = found; free(reply); return EGL_TRUE; } X11DisplayInstance *eplX11DisplayInstanceCreate(EplDisplay *pdpy, EGLBoolean from_init) { X11DisplayInstance *inst = NULL; int fd = -1; const char *gbmName = NULL; EGLDeviceEXT serverDevice = EGL_NO_DEVICE_EXT; EplInternalDisplay *internalDpy = NULL; EGLBoolean supportsDirect = EGL_FALSE; EGLBoolean supportsLinear = EGL_FALSE; inst = calloc(1, sizeof(X11DisplayInstance)); if (inst == NULL) { eplSetError(pdpy->platform, EGL_BAD_ALLOC, "Out of memory"); return NULL; } eplRefCountInit(&inst->refcount); inst->screen = pdpy->priv->screen_attrib; inst->platform = eplPlatformDataRef(pdpy->platform); if (pdpy->native_display == NULL) { int xcbScreen = 0; inst->own_display = EGL_TRUE; inst->conn = xcb_connect(pdpy->priv->display_env, &xcbScreen); if (inst->conn == NULL) { eplSetError(pdpy->platform, EGL_BAD_ACCESS, "Can't open display connection"); eplX11DisplayInstanceUnref(inst); return NULL; } if (inst->screen < 0) { inst->screen = xcbScreen; } } else { inst->own_display = EGL_FALSE; if (pdpy->platform_enum == EGL_PLATFORM_X11_KHR) { int xcbScreen = 0; inst->conn = eplX11GetXCBConnection(pdpy->native_display, &xcbScreen); if (inst->screen < 0) { inst->screen = xcbScreen; } } else { assert(pdpy->platform_enum == EGL_PLATFORM_XCB_EXT); inst->conn = (xcb_connection_t *) pdpy->native_display; } } if (inst->screen < 0) { // If we got here, then we're dealing with EGL_PLATFORM_XCB, and the // application didn't provide a screen number. char *host = NULL; int port = 0; assert(pdpy->platform_enum == EGL_PLATFORM_XCB_EXT); assert(!inst->own_display); if (!xcb_parse_display(pdpy->priv->display_env, &host, &port, &inst->screen) || inst->screen < 0) { inst->screen = 0; } free(host); } inst->xscreen = GetXCBScreen(inst->conn, inst->screen); if (inst->xscreen == NULL) { eplSetError(pdpy->platform, EGL_BAD_ALLOC, "Invalid screen number"); eplX11DisplayInstanceUnref(inst); return NULL; } if (!CheckServerExtensions(inst)) { if (from_init) { eplSetError(pdpy->platform, EGL_BAD_ACCESS, "X server is missing required extensions"); } eplX11DisplayInstanceUnref(inst); return NULL; } fd = GetDRI3DeviceFD(inst->conn, inst->xscreen); if (fd < 0) { eplSetError(pdpy->platform, EGL_BAD_ALLOC, "Can't open DRI3 device"); eplX11DisplayInstanceUnref(inst); return NULL; } serverDevice = FindDeviceForFD(pdpy->platform, fd); if (serverDevice != EGL_NO_DEVICE_EXT) { /* * The server is running on an NVIDIA device. NV->NV offloading doesn't * work, so our options are the server device or nothing. * * So, if the user/caller didn't request a specific device, or * requested the same device as the server device, then we're fine. * * Or, if PRIME is enabled, then we're allowed to pick a different * device, so we can still use the server device. */ if (pdpy->priv->requested_device == EGL_NO_DEVICE_EXT || pdpy->priv->requested_device == serverDevice || pdpy->priv->enable_alt_device) { inst->device = serverDevice; } else { if (!from_init && pdpy->priv->device_attrib != EGL_NO_DEVICE_EXT) { eplSetError(pdpy->platform, EGL_BAD_MATCH, "NV -> NV offloading is not supported"); } close(fd); eplX11DisplayInstanceUnref(inst); return NULL; } inst->supports_implicit_sync = EGL_FALSE; } else { /* * The server is not running on an NVIDIA device. * * If the user/caller requested a particular device, then use it. * * Otherwise, if PRIME is enabled, then we'll pick an arbitrary NVIDIA * device to use. * * Otherwise, we'll fail. If this is from eglGetPlatformDisplay, then * eglGetPlatformDisplay will fail and the next vendor library can try. */ if (pdpy->priv->requested_device != EGL_NO_DEVICE_EXT) { // Pick whatever device the user/caller requested. inst->device = pdpy->priv->requested_device; } else if (pdpy->priv->enable_alt_device) { // If PRIME is enabled, then pick an NVIDIA device. EGLint num = 0; if (!pdpy->platform->egl.QueryDevicesEXT(1, &inst->device, &num) || num <= 0) { inst->device = EGL_NO_DEVICE_EXT; } } inst->supports_implicit_sync = EGL_TRUE; } if (inst->device == EGL_NO_DEVICE_EXT) { if (from_init) { eplSetError(pdpy->platform, EGL_BAD_ACCESS, "X server is not running on an NVIDIA device"); } close(fd); eplX11DisplayInstanceUnref(inst); return NULL; } if (inst->device != serverDevice) { // If we're running on a different device than the server, then we need // to open the correct device node for GBM. const char *node; close(fd); node = pdpy->platform->egl.QueryDeviceStringEXT(inst->device, EGL_DRM_DEVICE_FILE_EXT); if (node == NULL) { eplSetError(pdpy->platform, EGL_BAD_ACCESS, "Can't find device node."); eplX11DisplayInstanceUnref(inst); return NULL; } fd = open(node, O_RDWR); if (fd < 0) { eplSetError(pdpy->platform, EGL_BAD_ACCESS, "Can't open device node %s", node); eplX11DisplayInstanceUnref(inst); return NULL; } inst->force_prime = EGL_TRUE; } inst->gbmdev = gbm_create_device(fd); if (inst->gbmdev == NULL) { eplSetError(pdpy->platform, EGL_BAD_ALLOC, "Can't open GBM device"); close(fd); eplX11DisplayInstanceUnref(inst); return NULL; } gbmName = gbm_device_get_backend_name(inst->gbmdev); if (gbmName == NULL || (strcmp(gbmName, "nvidia") != 0 && strcmp(gbmName, "nvidia_rm") != 0)) { // This should never happen. eplSetError(pdpy->platform, EGL_BAD_ACCESS, "Internal error: GBM device is not an NVIDIA device"); eplX11DisplayInstanceUnref(inst); return NULL; } internalDpy = eplGetDeviceInternalDisplay(pdpy->platform, inst->device); if (internalDpy == NULL) { eplSetError(pdpy->platform, EGL_BAD_ALLOC, "Can't create internal EGLDisplay"); eplX11DisplayInstanceUnref(inst); return NULL; } if (!eplInitializeInternalDisplay(pdpy->platform, internalDpy, NULL, NULL)) { eplX11DisplayInstanceUnref(inst); return NULL; } inst->internal_display = eplInternalDisplayRef(internalDpy); if (pdpy->platform->priv->egl.PlatformCopyColorBufferNVX != NULL && pdpy->platform->priv->egl.PlatformAllocColorBufferNVX != NULL && pdpy->platform->priv->egl.PlatformExportColorBufferNVX != NULL && pdpy->platform->priv->egl.CreateSync != NULL && pdpy->platform->priv->egl.DestroySync != NULL && pdpy->platform->priv->egl.WaitSync != NULL && pdpy->platform->priv->egl.DupNativeFenceFDANDROID != NULL) { const char *extensions = pdpy->platform->egl.QueryString(internalDpy->edpy, EGL_EXTENSIONS); // NV -> NV offloading doesn't currently work, because with our // driver, the X server can't use a pitch linear buffer as a // pixmap. if (serverDevice == EGL_NO_DEVICE_EXT) { inst->supports_prime = EGL_TRUE; } if (eplFindExtension("EGL_ANDROID_native_fence_sync", extensions)) { inst->supports_EGL_ANDROID_native_fence_sync = EGL_TRUE; } } if (!eplX11InitDriverFormats(pdpy->platform, inst)) { // This should never happen. If it does, then we've got a problem in // the driver. eplSetError(pdpy->platform, EGL_BAD_ALLOC, "No supported image formats from driver"); eplX11DisplayInstanceUnref(inst); return NULL; } if (!CheckServerFormatSupport(inst, &supportsDirect, &supportsLinear)) { eplSetError(pdpy->platform, EGL_BAD_ALLOC, "Can't get a format modifier list from the X server"); eplX11DisplayInstanceUnref(inst); return NULL; } if (!supportsLinear) { // If the server doesn't support pitch linear buffers, then we can't // use PRIME. inst->supports_prime = EGL_FALSE; } if (!supportsDirect) { // Note that this generally shouldn't happen unless we're using a // different device than the server. In any case, if the client and // server don't have any common format modifiers, then we'll have to // go through the PRIME path. inst->force_prime = EGL_TRUE; } if (!inst->supports_EGL_ANDROID_native_fence_sync) { // We need EGL_ANDROID_native_fence_sync to get a sync FD to plug in to // a timeline object. Likewise, implicit sync requires a sync FD to // attach to a dma-buf. inst->supports_explicit_sync = EGL_FALSE; inst->supports_implicit_sync = EGL_FALSE; } if (inst->supports_explicit_sync) { // Check if the DRM device supports timeline objects. uint64_t cap = 0; if (pdpy->platform->priv->drm.GetCap(fd, DRM_CAP_SYNCOBJ_TIMELINE, &cap) != 0 || cap == 0) { inst->supports_explicit_sync = EGL_FALSE; } } if (inst->force_prime && !inst->supports_prime) { if (from_init) { eplSetError(pdpy->platform, EGL_BAD_ALLOC, "No supported image formats from server"); } eplX11DisplayInstanceUnref(inst); return NULL; } if (from_init) { if (!eplX11InitConfigList(pdpy->platform, inst)) { eplX11DisplayInstanceUnref(inst); return NULL; } } return inst; } static void eplX11DisplayInstanceFree(X11DisplayInstance *inst) { eplConfigListFree(inst->configs); inst->configs = NULL; eplX11CleanupDriverFormats(inst); if (inst->conn != NULL && inst->own_display) { xcb_disconnect(inst->conn); } inst->conn = NULL; if (inst->gbmdev != NULL) { int fd = gbm_device_get_fd(inst->gbmdev); gbm_device_destroy(inst->gbmdev); close(fd); inst->gbmdev = NULL; } if (inst->platform != NULL) { if (!inst->platform->destroyed) { if (inst->internal_display != NULL) { eplTerminateInternalDisplay(inst->platform, inst->internal_display); } } eplPlatformDataUnref(inst->platform); } eplInternalDisplayUnref(inst->internal_display); free(inst); } static EGLBoolean eplX11InitializeDisplay(EplPlatformData *plat, EplDisplay *pdpy, EGLint *major, EGLint *minor) { assert(pdpy->priv->inst == NULL); if (eplX11IsNativeClosed(pdpy->priv->closed_callback)) { eplSetError(plat, EGL_BAD_ACCESS, "The native display has been closed"); return EGL_FALSE; } pdpy->priv->inst = eplX11DisplayInstanceCreate(pdpy, EGL_TRUE); if (pdpy->priv->inst == NULL) { return EGL_FALSE; } pdpy->internal_display = pdpy->priv->inst->internal_display->edpy; return EGL_TRUE; } static void eplX11TerminateDisplay(EplPlatformData *plat, EplDisplay *pdpy) { assert(pdpy->priv->inst != NULL); eplX11DisplayInstanceUnref(pdpy->priv->inst); pdpy->priv->inst = NULL; } static void eplX11DestroySurface(EplDisplay *pdpy, EplSurface *surf) { if (surf->type == EPL_SURFACE_TYPE_WINDOW) { eplX11DestroyWindow(surf); } else if (surf->type == EPL_SURFACE_TYPE_PIXMAP) { eplX11DestroyPixmap(surf); } else { assert(!"Invalid surface type."); } } static void eplX11FreeSurface(EplDisplay *pdpy, EplSurface *surf) { if (surf->type == EPL_SURFACE_TYPE_WINDOW) { eplX11FreeWindow(surf); } } static EGLBoolean eplX11WaitGL(EplDisplay *pdpy, EplSurface *psurf) { EGLBoolean ret = EGL_TRUE; pdpy->platform->priv->egl.Finish(); if (psurf != NULL && psurf->type == EPL_SURFACE_TYPE_WINDOW) { ret = eplX11WaitGLWindow(pdpy, psurf); } return ret; } EGLAttrib *eplX11GetInternalSurfaceAttribs(EplPlatformData *plat, EplDisplay *pdpy, const EGLAttrib *attribs) { EGLAttrib *internalAttribs = NULL; int count = 0; if (attribs != NULL) { for (count = 0; attribs[count] != EGL_NONE; count += 2) { if (attribs[count] == EGL_SURFACE_Y_INVERTED_NVX) { eplSetError(plat, EGL_BAD_ATTRIBUTE, "Invalid attribute 0x%04x\n", attribs[count]); return NULL; } } } internalAttribs = malloc((count + 3) * sizeof(EGLAttrib)); if (internalAttribs == NULL) { eplSetError(plat, EGL_BAD_ALLOC, "Out of memory\n"); return NULL; } memcpy(internalAttribs, attribs, count * sizeof(EGLAttrib)); internalAttribs[count] = EGL_SURFACE_Y_INVERTED_NVX; internalAttribs[count + 1] = EGL_TRUE; internalAttribs[count + 2] = EGL_NONE; return internalAttribs; } static EGLBoolean eplX11CheckImportSyncFileSupported(void) { EGLBoolean ret; pthread_mutex_lock(&import_sync_file_supported_mutex); ret = import_sync_file_supported; pthread_mutex_unlock(&import_sync_file_supported_mutex); return ret; } static void eplX11SetImportSyncFileUnsupported(void) { pthread_mutex_lock(&import_sync_file_supported_mutex); import_sync_file_supported = EGL_FALSE; pthread_mutex_unlock(&import_sync_file_supported_mutex); } EGLBoolean eplX11ImportDmaBufSyncFile(X11DisplayInstance *inst, int dmabuf, int syncfd) { EGLBoolean ret = EGL_FALSE; if (inst->supports_implicit_sync && eplX11CheckImportSyncFileSupported()) { struct dma_buf_import_sync_file params = {}; params.flags = DMA_BUF_SYNC_WRITE; params.fd = syncfd; if (drmIoctl(dmabuf, DMA_BUF_IOCTL_IMPORT_SYNC_FILE, ¶ms) == 0) { ret = EGL_TRUE; } else if (errno == ENOTTY || errno == EBADF || errno == ENOSYS) { eplX11SetImportSyncFileUnsupported(); } } return ret; } int eplX11ExportDmaBufSyncFile(X11DisplayInstance *inst, int dmabuf) { int fd = -1; if (inst->supports_implicit_sync && eplX11CheckImportSyncFileSupported()) { struct dma_buf_export_sync_file params = {}; params.flags = DMA_BUF_SYNC_WRITE; params.fd = -1; if (drmIoctl(dmabuf, DMA_BUF_IOCTL_EXPORT_SYNC_FILE, ¶ms) == 0) { fd = params.fd; } else if (errno == ENOTTY || errno == EBADF || errno == ENOSYS) { eplX11SetImportSyncFileUnsupported(); } } return fd; } EGLBoolean eplX11WaitForFD(int syncfd) { struct pollfd pfd; if (syncfd < 0) { return EGL_TRUE; } pfd.fd = syncfd; pfd.events = POLLIN; while (1) { int num = poll(&pfd, 1, -1); if (num == 1) { return EGL_TRUE; } else if (num < 0 && errno != EINTR) { return EGL_FALSE; } } } uint32_t eplX11GetNativeXID(EplDisplay *pdpy, void *native_surface, EGLBoolean create_platform) { unsigned long xid = 0; if (create_platform) { if (native_surface != NULL) { if (pdpy->platform_enum == EGL_PLATFORM_X11_KHR) { xid = *((unsigned long *) native_surface); } else { xid = *((uint32_t *) native_surface); } } } else { xid = (unsigned long) ((uintptr_t) native_surface); } // Make sure the value that we get actually fits in a 32-bit integer. if (((uint32_t) xid) != xid) { return 0; } return xid; } egl-x11-1.0.0/src/x11/x11-platform.h000066400000000000000000000372131471743126200165070ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: Copyright (c) 2024 NVIDIA CORPORATION & AFFILIATES. All rights reserved. * SPDX-License-Identifier: Apache-2.0 * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /** * \file * * Common structs for the Xlib/XCB platform library. * * Note that internally, we use XCB for everything, which allows using mostly * the same code for both EGL_KHR_platform_x11 and EGL_EXT_platform_xcb. */ #ifndef X11_PLATFORM_H #define X11_PLATFORM_H #include #include #include #include #include #include #include #include #include "platform-impl.h" #include "platform-utils.h" #include "driver-platform-surface.h" #include "config-list.h" #include "refcountobj.h" #ifndef EGL_EXT_platform_xcb #define EGL_EXT_platform_xcb 1 #define EGL_PLATFORM_XCB_EXT 0x31DC #define EGL_PLATFORM_XCB_SCREEN_EXT 0x31DE #endif /* EGL_EXT_platform_xcb */ #ifndef XCB_PRESENT_CAPABILITY_SYNCOBJ #define XCB_PRESENT_CAPABILITY_SYNCOBJ 16 #endif /** * Keeps track of a callback that we've registered with XESetCloseDisplay. * * These are used to check whether a native display has been closed. */ typedef struct _X11XlibDisplayClosedData X11XlibDisplayClosedData; EPL_REFCOUNT_DECLARE_TYPE_FUNCS(X11XlibDisplayClosedData, eplX11XlibDisplayClosedData); /** * Platform-specific stuff for X11. * * Currently, this just includes the OpenGL and EGL functions that we'll need. */ struct _EplImplPlatform { struct { PFNEGLQUERYDISPLAYATTRIBKHRPROC QueryDisplayAttribKHR; PFNEGLSWAPINTERVALPROC SwapInterval; PFNEGLQUERYDMABUFFORMATSEXTPROC QueryDmaBufFormatsEXT; PFNEGLQUERYDMABUFMODIFIERSEXTPROC QueryDmaBufModifiersEXT; PFNEGLCREATESYNCPROC CreateSync; PFNEGLDESTROYSYNCPROC DestroySync; PFNEGLWAITSYNCPROC WaitSync; PFNEGLDUPNATIVEFENCEFDANDROIDPROC DupNativeFenceFDANDROID; void (* Flush) (void); void (* Finish) (void); pfn_eglPlatformImportColorBufferNVX PlatformImportColorBufferNVX; pfn_eglPlatformFreeColorBufferNVX PlatformFreeColorBufferNVX; pfn_eglPlatformCreateSurfaceNVX PlatformCreateSurfaceNVX; pfn_eglPlatformSetColorBuffersNVX PlatformSetColorBuffersNVX; pfn_eglPlatformGetConfigAttribNVX PlatformGetConfigAttribNVX; pfn_eglPlatformCopyColorBufferNVX PlatformCopyColorBufferNVX; pfn_eglPlatformAllocColorBufferNVX PlatformAllocColorBufferNVX; pfn_eglPlatformExportColorBufferNVX PlatformExportColorBufferNVX; } egl; struct { xcb_void_cookie_t (* dri3_import_syncobj) (xcb_connection_t *c, uint32_t syncobj, xcb_drawable_t drawable, int32_t syncobj_fd); xcb_void_cookie_t (* dri3_free_syncobj) (xcb_connection_t *c, uint32_t syncobj); xcb_void_cookie_t (* present_pixmap_synced) (xcb_connection_t *c, xcb_window_t window, xcb_pixmap_t pixmap, uint32_t serial, xcb_xfixes_region_t valid, xcb_xfixes_region_t update, int16_t x_off, int16_t y_off, xcb_randr_crtc_t target_crtc, uint32_t acquire_syncobj, uint32_t release_syncobj, uint64_t acquire_point, uint64_t release_point, uint32_t options, uint64_t target_msc, uint64_t divisor, uint64_t remainder, uint32_t notifies_len, const xcb_present_notify_t *notifies); } xcb; struct { int (* GetCap) (int fd, uint64_t capability, uint64_t *value); int (* SyncobjCreate) (int fd, uint32_t flags, uint32_t *handle); int (* SyncobjDestroy) (int fd, uint32_t handle); int (* SyncobjHandleToFD) (int fd, uint32_t handle, int *obj_fd); int (* SyncobjFDToHandle) (int fd, int obj_fd, uint32_t *handle); int (* SyncobjImportSyncFile) (int fd, uint32_t handle, int sync_file_fd); int (* SyncobjExportSyncFile) (int fd, uint32_t handle, int *sync_file_fd); int (* SyncobjTimelineSignal) (int fd, const uint32_t *handles, uint64_t *points, uint32_t handle_count); int (* SyncobjTimelineWait) (int fd, uint32_t *handles, uint64_t *points, unsigned num_handles, int64_t timeout_nsec, unsigned flags, uint32_t *first_signaled); int (* SyncobjTransfer) (int fd, uint32_t dst_handle, uint64_t dst_point, uint32_t src_handle, uint64_t src_point, uint32_t flags); } drm; EGLBoolean timeline_funcs_supported; }; /** * Keeps track of format and format modifier support in the driver. * * This is used to cache the results of eglQueryDmaBufFormatsEXT and * eglQueryDmaBufModifiersEXT. */ typedef struct { // Put the fourcc code as the first element so that we can use bsearch // with just the fourcc code for a key. uint32_t fourcc; const EplFormatInfo *fmt; uint64_t *modifiers; int num_modifiers; uint64_t *external_modifiers; int num_external_modifiers; } X11DriverFormat; /** * Contains per-display data that's initialized in eglInitialize and then * doesn't change until eglTerminate. * * It's not safe to lock a display during the window update callback, because * you can end up with a deadlock. * * Thankfully, all of the data that the update callback needs is static and * doesn't change after initialization, so we don't actually need a mutex to * protect it. * * So, this struct contains all of the per-display data that the update * callback will need. * * This struct is also refcounted. If the app calls eglTerminate while another * thread still holds a reference to an EplSurface, then the X11DisplayInstance * struct will stick around until it finishes cleanup up the surface. * * The one exception to all of this is the xcb_connection_t itself. If the * app calls XCloseDisplay while another thread is trying to send or receive X * protocol, then it'll crash. But, if that happens, then that's very * definitely an app bug. */ typedef struct { EplRefCount refcount; /** * A reference to the \c EplPlatformData that this display came from. * * This is mainly here so that we can access the driver's EGL functions * without going through an EplDisplay, since this EplDisplayInstance might * have gone through an eglTerminate and eglInitialize, or might have been * destroyed entirely if we're going through teardown. */ EplPlatformData *platform; /** * The display connection. */ xcb_connection_t *conn; /** * True if the application passed NULL for the native display, so we had to * open our own display connection. */ EGLBoolean own_display; /** * The internal (driver) EGLDisplay. */ EplInternalDisplay *internal_display; /** * The screen number that we're talking to. */ int screen; /** * The xcb_screen_t struct for the screen that we're talking to. */ xcb_screen_t *xscreen; /** * The GBM device for whichever GPU we're rendering on. */ struct gbm_device *gbmdev; /** * The EGL device that we're rendering on. */ EGLDeviceEXT device; /** * If true, then always use of the indirect presentation path for PRIME. */ EGLBoolean force_prime; /** * If true, then we can support the PRIME presentation path. * * Note that this isn't necessarily the same as * \c EplImplDisplay::enable_alt_device. The \c supports_prime flag means that * we can use the PRIME presentation path as needed on a per-window basis, * even if we're not doing cross-device presentation. */ EGLBoolean supports_prime; /** * If true, then the driver supports EGL_ANDROID_native_fence_sync. */ EGLBoolean supports_EGL_ANDROID_native_fence_sync; /** * If true, then the server supports implicit sync semantics. */ EGLBoolean supports_implicit_sync; /** * If true, then we can use the new PresentPixmapSynced request for * synchronization on windows that support it. * * This means that we have the necessary EGL functions from the driver, * the necessary functions in libxcb and libdrm, and that the server * supports the necessary versions of the DRI3 and Present extensions. * * This does not account for the capabilities returned by the * PresentQueryCapabilties request. That's checked separately for each * window. */ EGLBoolean supports_explicit_sync; /** * The list of EGLConfigs. */ EplConfigList *configs; /** * The list of formats and modifiers that the driver supports. */ X11DriverFormat *driver_formats; int num_driver_formats; } X11DisplayInstance; /** * Contains all of the data we need for an EGLDisplay. */ struct _EplImplDisplay { /** * A copy of what the DISPLAY environment variable was when * eglGetPlatformDisplay was first called. */ char *display_env; /** * The screen number that the application specified in the * eglGetPlatformDisplay attribute list, or -1 if the app didn't specify. */ int screen_attrib; /** * The EGLDeviceEXT handle that was specified with an EGL_DEVICE_EXT * attribute. */ EGLDeviceEXT device_attrib; /** * The EGLDeviceEXT handle that we should use for rendering, or * EGL_NO_DEVICE_EXT to pick one during eglInitialize. * * This is set based on either the EGL_DEVICE_EXT attribute or based on * environment variables. */ EGLDeviceEXT requested_device; /** * If true, allow picking a different GPU to do rendering. * * This is set based on the __NV_PRIME_RENDER_OFFLOAD environment variable. * * If the normal device (\c requested_device if it's set, the server's * device otherwise) isn't usable, then the \c enable_alt_device flag tells * eplX11DisplayInstanceCreate to pick a different device rather than just * fail. * * Note that this flag doesn't mean that we will use the PRIME presentation * path. It's possible that we'd pick the same device as the server anyway. * * Likewise, if the application passed an EGL_DISPLAY_EXT attribute, then * we might end up doing cross-device presentation even if the user doesn't * set __NV_PRIME_RENDER_OFFLOAD. */ EGLBoolean enable_alt_device; /** * A pointer to the X11DisplayInstance struct, or NULL if this display isn't initialized. */ X11DisplayInstance *inst; /** * A callback to keep track of whether the native display has been closed. */ X11XlibDisplayClosedData *closed_callback; }; EPL_REFCOUNT_DECLARE_TYPE_FUNCS(X11DisplayInstance, eplX11DisplayInstance); /** * Returns the xcb_connection_t and the screen number for a native xlib * Display. * * This is a separate function so that we can avoid depending on Xlib in the * XCB platform library. */ xcb_connection_t *eplX11GetXCBConnection(void *native_display, int *ret_screen); /** * Registers a callback for when an Xlib Display is closed. * * Note that XCB doesn't have any equivalent to XESetCloseDisplay. */ X11XlibDisplayClosedData *eplX11AddXlibDisplayClosedCallback(void *xlib_native_display); /** * Returns the XID for the native surface handle in one of the * eglCreate*Surface functions. * * \param pdpy The EplDisplay struct * \param native_surface The native surface handle * \param create_platform True if this is for one of the * eglCreatePlatform*Surface functions. * \return The XID value, or 0 if the native handle is invalid. */ uint32_t eplX11GetNativeXID(EplDisplay *pdpy, void *native_surface, EGLBoolean create_platform); /** * Returns true if a native display has been closed. * * Note that this only works for an Xlib Display, because XCB doesn't have any * equivalent to XESetCloseDisplay. */ EGLBoolean eplX11IsNativeClosed(X11XlibDisplayClosedData *data); EGLBoolean eplX11LoadEGLExternalPlatformCommon(int major, int minor, const EGLExtDriver *driver, EGLExtPlatform *extplatform, EGLint platform_enum); EGLSurface eplX11CreatePixmapSurface(EplPlatformData *plat, EplDisplay *pdpy, EplSurface *surf, EGLConfig config, void *native_surface, const EGLAttrib *attribs, EGLBoolean create_platform); EGLSurface eplX11CreateWindowSurface(EplPlatformData *plat, EplDisplay *pdpy, EplSurface *surf, EGLConfig config, void *native_surface, const EGLAttrib *attribs, EGLBoolean create_platform); EGLBoolean eplX11SwapBuffers(EplPlatformData *plat, EplDisplay *pdpy, EplSurface *surf, const EGLint *rects, EGLint n_rects); /** * Initializes the list of driver formats. * * \param plat The platform data. * \param inst The X11DisplayInstance to fill in. * \return EGL_TRUE on success, or EGL_FALSE on failure. */ EGLBoolean eplX11InitDriverFormats(EplPlatformData *plat, X11DisplayInstance *inst); /** * Cleans up the format list that was initialized in eplX11InitDriverFormats. */ void eplX11CleanupDriverFormats(X11DisplayInstance *inst); /** * Finds the X11DriverFormat struct for a given format. */ X11DriverFormat *eplX11FindDriverFormat(X11DisplayInstance *inst, uint32_t fourcc); EGLBoolean eplX11InitConfigList(EplPlatformData *plat, X11DisplayInstance *inst); /** * Returns the list of EGL attributes (not the buffers/internal attributes) * that should be passed to eglPlatformCreateSurfaceNVX. * * Currently, this just sets the EGL_WAYLAND_Y_INVERTED_WL flag to true, and * passes any other attributes through. * * \param plat The platform data * \param pdpy The display data * \param attribs The attribute list that was passed to eglCreateWindowSurface * or eglCreatePixmapSurface. * \return The EGLAttrib array to pass to the driver, or NULL on error. The * caller must free the array using free(). */ EGLAttrib *eplX11GetInternalSurfaceAttribs(EplPlatformData *plat, EplDisplay *pdpy, const EGLAttrib *attribs); EGLBoolean eplX11HookChooseConfig(EGLDisplay edpy, EGLint const *attribs, EGLConfig *configs, EGLint configSize, EGLint *numConfig); EGLBoolean eplX11HookGetConfigAttrib(EGLDisplay edpy, EGLConfig config, EGLint attribute, EGLint *value); void eplX11DestroyPixmap(EplSurface *surf); EGLBoolean eplX11SwapInterval(EGLDisplay edpy, EGLint interval); void eplX11DestroyWindow(EplSurface *surf); void eplX11FreeWindow(EplSurface *surf); EGLBoolean eplX11WaitGLWindow(EplDisplay *pdpy, EplSurface *psurf); /** * A wrapper around the DMA_BUF_IOCTL_IMPORT_SYNC_FILE ioctl. * * This will check whether implicit sync is supporte, and if so, it will * plug a syncfd into a dma-buf. */ EGLBoolean eplX11ImportDmaBufSyncFile(X11DisplayInstance *inst, int dmabuf, int syncfd); int eplX11ExportDmaBufSyncFile(X11DisplayInstance *inst, int dmabuf); /** * Waits for a file descriptor to be ready using poll(). * * Since this uses poll(), it can work with any arbitrary file descriptor * (including a syncfd or a dma-buf), but it does so with a CPU stall. * * \param syncfd The file descriptor to wait on. * \return EGL_TRUE on success, or EGL_FALSE on error. */ EGLBoolean eplX11WaitForFD(int syncfd); #endif // X11_PLATFORM_H egl-x11-1.0.0/src/x11/x11-timeline.c000066400000000000000000000101151471743126200164540ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: Copyright (c) 2024 NVIDIA CORPORATION & AFFILIATES. All rights reserved. * SPDX-License-Identifier: Apache-2.0 * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "x11-timeline.h" #include #include #include EGLBoolean eplX11TimelineInit(X11DisplayInstance *inst, X11Timeline *timeline) { int fd = -1; int ret; memset(timeline, 0, sizeof(*timeline)); if (!inst->supports_explicit_sync) { assert(inst->supports_explicit_sync); return EGL_FALSE; } ret = inst->platform->priv->drm.SyncobjCreate( gbm_device_get_fd(inst->gbmdev), 0, &timeline->handle); if (ret != 0) { return EGL_FALSE; } ret = inst->platform->priv->drm.SyncobjHandleToFD( gbm_device_get_fd(inst->gbmdev), timeline->handle, &fd); if (ret != 0) { inst->platform->priv->drm.SyncobjDestroy( gbm_device_get_fd(inst->gbmdev), timeline->handle); close(fd); return EGL_FALSE; } timeline->xid = xcb_generate_id(inst->conn); // Note that libxcb will close the file descriptor after it sends the // request, so we do *not* close it here. inst->platform->priv->xcb.dri3_import_syncobj(inst->conn, timeline->xid, inst->xscreen->root, fd); return EGL_TRUE; } void eplX11TimelineDestroy(X11DisplayInstance *inst, X11Timeline *timeline) { // The XID is non-zero if and only if eplX11TimelineInit was successfully // called. if (timeline->xid != 0) { inst->platform->priv->xcb.dri3_free_syncobj(inst->conn, timeline->xid); timeline->xid = 0; inst->platform->priv->drm.SyncobjDestroy( gbm_device_get_fd(inst->gbmdev), timeline->handle); timeline->handle = 0; } } int eplX11TimelinePointToSyncFD(X11DisplayInstance *inst, X11Timeline *timeline) { uint32_t tempobj = 0; int syncfd = -1; if (inst->platform->priv->drm.SyncobjCreate( gbm_device_get_fd(inst->gbmdev), 0, &tempobj) != 0) { return EGL_FALSE; } if (inst->platform->priv->drm.SyncobjTransfer(gbm_device_get_fd(inst->gbmdev), tempobj, 0, timeline->handle, timeline->point, 0) != 0) { goto done; } if (inst->platform->priv->drm.SyncobjExportSyncFile(gbm_device_get_fd(inst->gbmdev), tempobj, &syncfd) != 0) { goto done; } done: inst->platform->priv->drm.SyncobjDestroy( gbm_device_get_fd(inst->gbmdev), tempobj); return syncfd; } EGLBoolean eplX11TimelineAttachSyncFD(X11DisplayInstance *inst, X11Timeline *timeline, int syncfd) { uint32_t tempobj = 0; EGLBoolean success = EGL_FALSE; assert(syncfd >= 0); if (inst->platform->priv->drm.SyncobjCreate( gbm_device_get_fd(inst->gbmdev), 0, &tempobj) != 0) { // TODO: Issue an EGL error here? return EGL_FALSE; } if (inst->platform->priv->drm.SyncobjImportSyncFile( gbm_device_get_fd(inst->gbmdev), tempobj, syncfd) != 0) { goto done; } if (inst->platform->priv->drm.SyncobjTransfer( gbm_device_get_fd(inst->gbmdev), timeline->handle, timeline->point + 1, tempobj, 0, 0) != 0) { goto done; } timeline->point++; success = EGL_TRUE; done: inst->platform->priv->drm.SyncobjDestroy( gbm_device_get_fd(inst->gbmdev), tempobj); return success; } egl-x11-1.0.0/src/x11/x11-timeline.h000066400000000000000000000032751471743126200164720ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: Copyright (c) 2024 NVIDIA CORPORATION & AFFILIATES. All rights reserved. * SPDX-License-Identifier: Apache-2.0 * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef X11_TIMELINE_H #define X11_TIMELINE_H /** * \file * * Functions for dealing with timeline sync objects. */ #include #include #include "x11-platform.h" typedef struct { uint32_t handle; uint32_t xid; uint64_t point; } X11Timeline; /** * Creates and initializes a timeline sync object. * * This will create a timeline object, and share it with the server using DRI3. */ EGLBoolean eplX11TimelineInit(X11DisplayInstance *inst, X11Timeline *timeline); void eplX11TimelineDestroy(X11DisplayInstance *inst, X11Timeline *timeline); /** * Attaches a sync FD to the next timeline point. * * On a successful return, \c timeline->point will be the timeline point where * the sync FD was attached. */ EGLBoolean eplX11TimelineAttachSyncFD(X11DisplayInstance *inst, X11Timeline *timeline, int syncfd); /** * Extracts a sync FD from the current timeline point. */ int eplX11TimelinePointToSyncFD(X11DisplayInstance *inst, X11Timeline *timeline); #endif // X11_TIMELINE_H egl-x11-1.0.0/src/x11/x11-window.c000066400000000000000000002154551471743126200161730ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: Copyright (c) 2024 NVIDIA CORPORATION & AFFILIATES. All rights reserved. * SPDX-License-Identifier: Apache-2.0 * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /** * \file * * Window handling for X11. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include "x11-platform.h" #include "x11-timeline.h" #include "glvnd_list.h" #include "dma-buf.h" /** * This is a special flag that XWayland sets in a PresentConfigureNotify event * when the window gets destroyed. Note that it's not actually specified in the * Present spec yet. */ #define PRESENT_WINDOW_DESTROYED_FLAG (1 << 0) /** * The maximum number of color buffers to allocate for a window. */ static const int MAX_COLOR_BUFFERS = 4; /** * The maximum number of linear buffers for PRIME presentation. */ static const int MAX_PRIME_BUFFERS = 2; /** * The maximum number of outstanding PresentPixmap requests that we can have * before we wait for one to complete in eglSwapBuffers. */ static const uint32_t MAX_PENDING_FRAMES = 1; /** * How long to wait for a buffer release before we stop to check for window * events. */ static const int RELEASE_WAIT_TIMEOUT = 100; /** * An enum to keep track of whether it's safe to reuse a buffer. */ typedef enum { /** * The buffer is idle, so we can use it immediately. */ BUFFER_STATUS_IDLE, /** * The buffer is in use in the server, and we have not yet received a * PresentIdleNotify event for it. */ BUFFER_STATUS_IN_USE, /** * We've received a PresentIdleNotify event for this buffer, but we haven't * waited for it to actually be free yet. */ BUFFER_STATUS_IDLE_NOTIFIED, } X11BufferStatus; /** * Data for each color buffer that we allocate for a window. * * This contains the EGLExtColorBuffer handle for the buffer in the driver, and * the XID for the corresponding Pixmap in the server. */ typedef struct { /** * The GBM buffer object for this color buffer. */ struct gbm_bo *gbo; /** * The handle for the color buffer in the driver. */ EGLPlatformColorBufferNVX buffer; /** * Whether this buffer is still in use by the server. */ X11BufferStatus status; /** * The XID for the pixmap that uses this buffer. */ xcb_pixmap_t xpix; /** * The serial number of the last PresentPixmap request that used this * buffer. */ uint32_t last_present_serial; /** * A file descriptor for the dma-buf. * * Note that currently, this is only used for PRIME buffers. */ int fd; /** * A timeline sync object. * * It's possible that different buffers could go through a different * presentation path in the server, which could in turn cause them to be * released in a different order than they were presented. * * To cope with that, we give each buffer its own timeline for acquire and * release points. */ X11Timeline timeline; struct glvnd_list entry; } X11ColorBuffer; /** * Data that we need to keep track of for an X window. */ typedef struct { /// A pointer back to the owning display. X11DisplayInstance *inst; xcb_window_t xwin; /** * A mutex to protect the other fields in this struct. * * We have to be careful with this to avoid a deadlock. The driver calls * the window update callback while holding its window-system lock, and the * update callback has to take the window mutex to safely modify the other * fields. * * That means that we can't call into the driver while holding this mutex, * if there's any chance that another thread could be in the middle of the * update callback. If we did, then that driver call could try to take the * window-system lock, which would then deadlock. * * To deal with that, we rely on the fact that the driver will only call * the update callback for a surface if that surface is current (or about * to be current in an eglMakeCurrent call). That means that we can safely * call into the driver from eglSwapBuffers while holding this mutex, * because eglSwapBuffers is also only valid if an EGLSurface is current. */ pthread_mutex_t mutex; /** * The capabilities of the window, as reported by PresentQueryCapabilities. */ uint32_t present_capabilities; /** * If true, then we use explicit sync for this surface. */ EGLBoolean use_explicit_sync; /** * The current size of the window. */ EGLint width; EGLint height; /** * The format modifiers that we're using for this window. */ uint64_t modifier; /** * True if GPU offloading is in use. * * In the normal (non-PRIME) case, we render directly to the color buffer * that's shared with the server. * * In the PRIME case, we render to a private back buffer, and then in * eglSwapBuffers, we copy the back buffer to a shared intermediate buffer, * which is then presented. */ EGLBoolean prime; /** * The pending width and height is set in response to a window resize. * * We set this when we receive a PresentConfigureNotify event. * * If the pending size is different than the current size, then that means * we need to reallocate the shared color buffers for this window. */ EGLint pending_width; EGLint pending_height; /** * True if we should check for new format modifiers for the color buffers. */ EGLBoolean needs_modifier_check; /** * If this is non-zero, then ignore the update callback. * * This is used in eglSwapBuffers and during teardown. */ unsigned int skip_update_callback; /** * The color buffers that we've allocated for this window. * * This is a list of X11ColorBuffer structs. */ struct glvnd_list color_buffers; /** * A list of pitch linear buffers used for PRIME. */ struct glvnd_list prime_buffers; /** * A pointer to the current front buffer. */ X11ColorBuffer *current_front; /** * A pointer to the current back buffer. */ X11ColorBuffer *current_back; /** * A pointer to the current shared buffer for PRIME. */ X11ColorBuffer *current_prime; /** * The current swap interval, as set by eglSwapInterval. */ EGLint swap_interval; /** * The color format that we're using for this window. */ const X11DriverFormat *format; uint32_t present_event_id; uint32_t present_event_stamp; xcb_special_event_t *present_event; /** * The serial number of the last PresentPixmap request that we sent. */ uint32_t last_present_serial; /** * The serial number of the last PresentCompleteNotify event that we * recieved. */ uint32_t last_complete_serial; /** * The MSC value from the last PresentCompleteNotify event that we received. */ uint64_t last_complete_msc; /** * Set to true if the native window was destroyed. * * This requires a fairly recent version of the X server. */ EGLBoolean native_destroyed; } X11Window; static void FreeColorBuffer(X11DisplayInstance *inst, X11ColorBuffer *buffer) { if (buffer != NULL) { if (buffer->gbo != NULL) { gbm_bo_destroy(buffer->gbo); } if (buffer->buffer != NULL) { inst->platform->priv->egl.PlatformFreeColorBufferNVX( inst->internal_display->edpy, buffer->buffer); } if (buffer->xpix != 0) { if (inst->conn != NULL) { // TODO: Is it safe to call into xcb if this happens during teardown? xcb_free_pixmap(inst->conn, buffer->xpix); } } eplX11TimelineDestroy(inst, &buffer->timeline); if (buffer->fd >= 0) { close(buffer->fd); } free(buffer); } } /** * Allocates a color buffer in the driver. This does *not* create a shared * pixmap from the buffer. */ static X11ColorBuffer *AllocOneColorBuffer(X11DisplayInstance *inst, const EplFormatInfo *fmt, uint32_t width, uint32_t height, const uint64_t *modifiers, int num_modifiers, EGLBoolean scanout) { int fd = -1; uint32_t flags = 0; X11ColorBuffer *buffer = NULL; assert(num_modifiers > 0); if (scanout) { flags |= GBM_BO_USE_SCANOUT; } buffer = calloc(1, sizeof(X11ColorBuffer)); if (buffer == NULL) { return NULL; } glvnd_list_init(&buffer->entry); buffer->fd = -1; buffer->gbo = gbm_bo_create_with_modifiers2(inst->gbmdev, width, height, fmt->fourcc, modifiers, num_modifiers, flags); if (buffer->gbo == NULL) { goto done; } fd = gbm_bo_get_fd(buffer->gbo); if (fd < 0) { goto done; } buffer->buffer = inst->platform->priv->egl.PlatformImportColorBufferNVX(inst->internal_display->edpy, fd, width, height, gbm_bo_get_format(buffer->gbo), gbm_bo_get_stride(buffer->gbo), gbm_bo_get_offset(buffer->gbo, 0), gbm_bo_get_modifier(buffer->gbo)); if (buffer->buffer == NULL) { goto done; } done: if (fd >= 0) { close(fd); } if (buffer->buffer == NULL) { FreeColorBuffer(inst, buffer); return NULL; } return buffer; } /** * Allocates a linear sysmem buffer to use for PRIME. */ static X11ColorBuffer *AllocatePrimeBuffer(X11DisplayInstance *inst, uint32_t fourcc, uint32_t width, uint32_t height) { X11ColorBuffer *buffer; struct gbm_import_fd_modifier_data gimport; int stride = 0; int offset = 0; EGLBoolean success = EGL_FALSE; buffer = calloc(1, sizeof(X11ColorBuffer)); if (buffer == NULL) { return NULL; } glvnd_list_init(&buffer->entry); buffer->fd = -1; buffer->buffer = inst->platform->priv->egl.PlatformAllocColorBufferNVX(inst->internal_display->edpy, width, height, fourcc, DRM_FORMAT_MOD_LINEAR, EGL_TRUE); if (buffer->buffer == NULL) { goto done; } // Export the image to a dma-buf. if (!inst->platform->priv->egl.PlatformExportColorBufferNVX(inst->internal_display->edpy, buffer->buffer, &buffer->fd, NULL, NULL, NULL, &stride, &offset, NULL)) { goto done; } // Import the dma-buf to a gbm_bo, so that we can use it in // CreateSharedPixmap. gimport.width = width; gimport.height = height; gimport.format = fourcc; gimport.num_fds = 1; gimport.fds[0] = buffer->fd; gimport.strides[0] = stride; gimport.offsets[0] = offset; gimport.modifier = DRM_FORMAT_MOD_LINEAR; buffer->gbo = gbm_bo_import(inst->gbmdev, GBM_BO_IMPORT_FD_MODIFIER, &gimport, 0); if (buffer->gbo == NULL) { goto done; } success = EGL_TRUE; done: if (!success) { FreeColorBuffer(inst, buffer); return NULL; } return buffer; } static void FreeWindowBuffers(EplSurface *surf) { X11Window *pwin = (X11Window *) surf->priv; while (!glvnd_list_is_empty(&pwin->color_buffers)) { X11ColorBuffer *buffer = glvnd_list_first_entry(&pwin->color_buffers, X11ColorBuffer, entry); glvnd_list_del(&buffer->entry); FreeColorBuffer(pwin->inst, buffer); } while (!glvnd_list_is_empty(&pwin->prime_buffers)) { X11ColorBuffer *buffer = glvnd_list_first_entry(&pwin->prime_buffers, X11ColorBuffer, entry); glvnd_list_del(&buffer->entry); FreeColorBuffer(pwin->inst, buffer); } pwin->current_front = NULL; pwin->current_back = NULL; pwin->current_prime = NULL; } static EGLBoolean AllocWindowBuffers(EplSurface *surf, const uint64_t *modifiers, int num_modifiers, EGLBoolean prime) { X11Window *pwin = (X11Window *) surf->priv; uint64_t modifier; X11ColorBuffer *front = NULL; X11ColorBuffer *back = NULL; X11ColorBuffer *shared = NULL; EGLPlatformColorBufferNVX sharedBuf = NULL; EGLBoolean success = EGL_TRUE; front = AllocOneColorBuffer(pwin->inst, pwin->format->fmt, pwin->pending_width, pwin->pending_height, modifiers, num_modifiers, !prime); if (front == NULL) { goto done; } // We let the driver pick the modifier when we allocate the front buffer, // and then we'll just re-use that same modifier for everything after that. modifier = gbm_bo_get_modifier(front->gbo); back = AllocOneColorBuffer(pwin->inst, pwin->format->fmt, pwin->pending_width, pwin->pending_height, &modifier, 1, !prime); if (back == NULL) { goto done; } if (prime) { /* * For PRIME, we need to allocate one linear buffer so that we can * attach it as the blit target. */ shared = AllocatePrimeBuffer(pwin->inst, pwin->format->fmt->fourcc, pwin->pending_width, pwin->pending_height); if (shared == NULL) { goto done; } sharedBuf = shared->buffer; } if (surf->internal_surface != EGL_NO_SURFACE) { EGLAttrib buffers[] = { GL_FRONT, (EGLAttrib) front->buffer, GL_BACK, (EGLAttrib) back->buffer, EGL_PLATFORM_SURFACE_BLIT_TARGET_NVX, (EGLAttrib) sharedBuf, EGL_NONE }; if (!pwin->inst->platform->priv->egl.PlatformSetColorBuffersNVX(pwin->inst->internal_display->edpy, surf->internal_surface, buffers)) { goto done; } } FreeWindowBuffers(surf); glvnd_list_add(&front->entry, &pwin->color_buffers); glvnd_list_add(&back->entry, &pwin->color_buffers); if (shared != NULL) { glvnd_list_append(&shared->entry, &pwin->prime_buffers); } pwin->current_front = front; pwin->current_back = back; pwin->current_prime = shared; pwin->width = pwin->pending_width; pwin->height = pwin->pending_height; pwin->modifier = modifier; pwin->prime = prime; success = EGL_TRUE; done: if (!success) { FreeColorBuffer(pwin->inst, front); FreeColorBuffer(pwin->inst, back); FreeColorBuffer(pwin->inst, shared); } return success; } /** * Finds the intersection between two sets of modifiers. * * This function will change \p mods to be the intersection between * \p client_mods and \p server_mods. * * If the intersection is empty, then \p mods will be unchanged. * * \param[out] mods Returns the intersection of modifiers. This may be the * same pointer as \p client_mods to update the list in place. * \param client_mods The list of supported modifiers in the client. * \param num_client_mods The number of elements in \p client_mods. * \param server_mods The list of supported modifiers in the server. * \param num_server_mods The number of elements in \p server_mods. * * \return The number of elements in the intersection. */ static int GetModifierIntersection(uint64_t *mods, const uint64_t *client_mods, int num_client_mods, const uint64_t *server_mods, int num_server_mods) { int count = 0; int i, j; for (i=0; ifourcc); if (driverFmt == NULL) { // We should have already checked this when we set up the EGLConfig // list. assert(!"Can't happen -- driver doesn't support format."); return EGL_FALSE; } mods = malloc(driverFmt->num_modifiers * sizeof(uint64_t)); if (mods == NULL) { return EGL_FALSE; } if (!inst->force_prime) { cookie = xcb_dri3_get_supported_modifiers(inst->conn, xwin, eplFormatInfoDepth(format->fmt), format->fmt->bpp); reply = xcb_dri3_get_supported_modifiers_reply(inst->conn, cookie, &error); if (reply == NULL) { free(error); free(mods); return EGL_FALSE; } if (xcb_dri3_get_supported_modifiers_window_modifiers_length(reply) > 0) { numMods = GetModifierIntersection(mods, driverFmt->modifiers, driverFmt->num_modifiers, xcb_dri3_get_supported_modifiers_window_modifiers(reply), xcb_dri3_get_supported_modifiers_window_modifiers_length(reply)); } if (numMods == 0) { /* * If the window list is not empty, then we assume that any * modifier not in that list will require a blit, so we might as * well use the PRIME path and do that blit in the client. * * If the window list is empty, then that means the server doesn't * have a separate per-window modifier list, so look for something * in the screen list instead. * * Likewise, if we can't support PRIME in the client, then try to * find something that the server supports, even if that means * letting the server do a blit. */ if (xcb_dri3_get_supported_modifiers_window_modifiers_length(reply) == 0 || !inst->supports_prime) { numMods = GetModifierIntersection(mods, driverFmt->modifiers, driverFmt->num_modifiers, xcb_dri3_get_supported_modifiers_screen_modifiers(reply), xcb_dri3_get_supported_modifiers_screen_modifiers_length(reply)); } } free(reply); } if (numMods > 0) { prime = EGL_FALSE; } else if (inst->supports_prime) { /* * If we didn't find a usable modifier, then we have to use the PRIME * presentation path. * * In this case, we don't care what the server supports, because the * color buffers will only be used by the client, and the shared pixmap * will use DRM_FORMAT_MOD_LINEAR. */ prime = EGL_TRUE; memcpy(mods, driverFmt->modifiers, driverFmt->num_modifiers * sizeof(uint64_t)); numMods = driverFmt->num_modifiers; } else { // We couldn't find any supported modifiers. free(mods); return EGL_FALSE; } *ret_modifiers = mods; *ret_num_modifiers = numMods; *ret_prime = prime; return EGL_TRUE; } void eplX11FreeWindow(EplSurface *surf) { X11Window *pwin = (X11Window *) surf->priv; FreeWindowBuffers(surf); if (pwin->inst->conn != NULL && pwin->present_event != NULL) { // Unregister for events. It's possible that the window has already // been destroyed since the last time we checked for events, so // ignore any errors. if (!pwin->native_destroyed) { xcb_void_cookie_t cookie = xcb_present_select_input_checked(pwin->inst->conn, pwin->present_event_id, pwin->xwin, 0); xcb_discard_reply(pwin->inst->conn, cookie.sequence); } xcb_unregister_for_special_event(pwin->inst->conn, pwin->present_event); } surf->priv = NULL; pthread_mutex_destroy(&pwin->mutex); eplX11DisplayInstanceUnref(pwin->inst); free(pwin); } static void HandlePresentEvent(EplSurface *surf, xcb_generic_event_t *xcbevt) { X11Window *pwin = (X11Window *) surf->priv; xcb_present_generic_event_t *ge = (xcb_present_generic_event_t *) xcbevt; if (ge->evtype == XCB_PRESENT_CONFIGURE_NOTIFY) { xcb_present_configure_notify_event_t *evt = (xcb_present_configure_notify_event_t *) xcbevt; pwin->pending_width = evt->width; pwin->pending_height = evt->height; if (evt->pixmap_flags & PRESENT_WINDOW_DESTROYED_FLAG) { pwin->native_destroyed = EGL_TRUE; } } else if (ge->evtype == XCB_PRESENT_IDLE_NOTIFY) { // With explicit sync, we don't care about PresentIdleNotify events. if (!pwin->use_explicit_sync) { X11ColorBuffer *buffer; xcb_present_idle_notify_event_t *evt = (xcb_present_idle_notify_event_t *) xcbevt; struct glvnd_list *buffers = pwin->prime ? &pwin->prime_buffers : &pwin->color_buffers; glvnd_list_for_each_entry(buffer, buffers, entry) { if (buffer->xpix == evt->pixmap && buffer->last_present_serial == evt->serial) { assert(buffer->status == BUFFER_STATUS_IN_USE); if (buffer->status == BUFFER_STATUS_IN_USE) { buffer->status = BUFFER_STATUS_IDLE_NOTIFIED; } buffer->last_present_serial = 0; /* * Move the buffer to the end of the list. If we don't have any * server -> client synchronization, then this ensures that * we'll reuse the oldest buffers first, so we'll have the best * chance that the buffer really is idle. */ glvnd_list_del(&buffer->entry); glvnd_list_append(&buffer->entry, buffers); break; } } } } else if (ge->evtype == XCB_PRESENT_COMPLETE_NOTIFY) { xcb_present_complete_notify_event_t *evt = (xcb_present_complete_notify_event_t *) xcbevt; uint32_t age = pwin->last_present_serial - evt->serial; uint32_t pending = pwin->last_present_serial - pwin->last_complete_serial; if (age < pending) { pwin->last_complete_serial = evt->serial; pwin->last_complete_msc = evt->msc; } if (!pwin->inst->force_prime && evt->mode == XCB_PRESENT_COMPLETE_MODE_SUBOPTIMAL_COPY) { /* * If the server tells us that this is a suboptimal format, then we * should check for supported format modifiers during the next * swap. */ pwin->needs_modifier_check = EGL_TRUE; } } else { // We shouldn't get here. assert(!"Invalid present event"); } } /** * Checks if we need to reallocate the buffers for a window, and if so, * reallocates them. * * \param surf The surface to check. * \param allow_modifier_change If true, then allow reallocating the buffers to * deal with a format modifier change. * \param[out] was_resized Optionally returns EGL_TRUE if the window has new * buffers. * \return EGL_TRUE on success, or EGL_FALSE if there was an error. */ static EGLBoolean CheckReallocWindow(EplSurface *surf, EGLBoolean allow_modifier_change, EGLBoolean *was_resized) { X11Window *pwin = (X11Window *) surf->priv; EGLBoolean need_realloc = EGL_FALSE; if (was_resized) { *was_resized = EGL_FALSE; } if (surf->deleted || pwin->native_destroyed) { return EGL_TRUE; } if (pwin->pending_width != pwin->width || pwin->pending_height != pwin->height) { need_realloc = EGL_TRUE; } if (need_realloc || (allow_modifier_change && pwin->needs_modifier_check)) { uint64_t currentModifier = pwin->modifier; const uint64_t *mods = NULL; uint64_t *modsBuffer = NULL; int numMods = 0; EGLBoolean prime = EGL_FALSE; if (pwin->needs_modifier_check) { if (!FindSupportedModifiers(pwin->inst, pwin->format, pwin->xwin, &modsBuffer, &numMods, &prime)) { return EGL_FALSE; } mods = modsBuffer; /* * Check if the current modifier is one of the supported ones. * If it is, then we don't need to reallocate just for the * modifier change. * * If we need to reallocate anyway because the window resized, * though, then pick new modifiers while we're at it. */ if (!need_realloc && allow_modifier_change) { int i; need_realloc = EGL_TRUE; for (i=0; imodifier == mods[i]) { need_realloc = EGL_FALSE; break; } } } } else { // We didn't get a notification from the server that the modifier // is suboptimal, so just keep the current modifier. mods = ¤tModifier; prime = pwin->prime; numMods = 1; } if (need_realloc) { if (!AllocWindowBuffers(surf, mods, numMods, prime)) { free(modsBuffer); return EGL_FALSE; } if (was_resized != NULL) { *was_resized = EGL_TRUE; } pwin->needs_modifier_check = EGL_FALSE; } else if (allow_modifier_change) { // If we checked for new modifiers, but we didn't need to // reallocate, then clear the needs_modifier_check flag so that we // don't end up checking again on the next frame. pwin->needs_modifier_check = EGL_FALSE; } free(modsBuffer); } return EGL_TRUE; } static void PollForWindowEvents(EplSurface *surf) { X11Window *pwin = (X11Window *) surf->priv; while (!pwin->native_destroyed && !surf->deleted) { xcb_generic_event_t *xcbevt = xcb_poll_for_special_event(pwin->inst->conn, pwin->present_event); if (xcbevt == NULL) { break; } HandlePresentEvent(surf, xcbevt); free(xcbevt); } } static void WindowUpdateCallback(void *param) { EplSurface *surf = param; X11Window *pwin = (X11Window *) surf->priv; /* * Here, we lock the window mutex, but *not* the display mutex. * * We can't lock the display mutex, because we could run into a deadlock: * - Thread A calls an EGL function in the platform library, which locks * the display. The platform library calls into the driver, which tries to * take the winsys lock. * - Thread B calls a GL function. The driver takes the winsys lock, then * calls the window update callback. The window update callback tries to * take the display lock. * * We can safely access anything in X11DisplayInstance, because that never * changes after it's initialized, and thus we don't need a mutex for it. */ pthread_mutex_lock(&pwin->mutex); if (pwin->skip_update_callback != 0) { pthread_mutex_unlock(&pwin->mutex); return; } PollForWindowEvents(surf); CheckReallocWindow(surf, EGL_FALSE, NULL); pthread_mutex_unlock(&pwin->mutex); } /** * A common helper function to send a PresentPixmap or PresentPixmapSynced * request. * * If explicit sync is supported, then the pixmap's current timeline point must * already be set up to the correct acquire fence. */ static void SendPresentPixmap(EplSurface *surf, X11ColorBuffer *sharedPixmap, uint32_t options) { X11Window *pwin = (X11Window *) surf->priv; uint32_t numPending = pwin->last_present_serial - pwin->last_complete_serial; uint32_t targetMSC = 0; uint64_t divisor = 1; if (pwin->swap_interval <= 0) { options |= XCB_PRESENT_OPTION_ASYNC; } if (options & XCB_PRESENT_OPTION_ASYNC) { // Make sure that the server actually supports the async flag. If it // doesn't, then just remove it. if (!(pwin->present_capabilities & XCB_PRESENT_CAPABILITY_ASYNC)) { options &= ~XCB_PRESENT_OPTION_ASYNC; } targetMSC = 0; } else { /* * Note the the semantics of PresentPixmap doesn't quite match what * eglSwapBuffers is supposed to do. * * With a swap interval >= 1, each image must be displayed for at least * that many refresh cycles (or with a compositor, for that many * frames) before switching to the next image. * * But, PresentPixmap only accepts an absolute value for the MSC * target, not one that's relative to the previous present. So, to do * this correctly, we'd have to set the MSC target to the previous * frame's MSC value plus the swap interval. * * But, we don't want to stall and wait the last PresentPixmap to * complete, so we don't necessarily know what the last MSC value was. * * So instead, set the MSC target based on the most recent * PresentCompleteNotify event that we did get, and just increment it * based on the number of outstanding frames. * * That should give the correct result as long as you've got a * relatively steady framerate: * - If the framerate is at least as fast as the refresh rate, then * we should have one PresentPixmap request complete per refresh * cycle, and so adding the number of pending frames will be correct. * - If the framerate is slower than the refresh rate, then the * previous Present will have completed by the time we send the next * one, so we actually will have the most recent MSC value from the * server. */ targetMSC = pwin->last_complete_msc + ((numPending + 1) * pwin->swap_interval); } pwin->last_present_serial++; if (pwin->use_explicit_sync) { pwin->inst->platform->priv->xcb.present_pixmap_synced(pwin->inst->conn, pwin->xwin, sharedPixmap->xpix, pwin->last_present_serial, 0, 0, 0, 0, 0, sharedPixmap->timeline.xid, sharedPixmap->timeline.xid, sharedPixmap->timeline.point, sharedPixmap->timeline.point + 1, options, targetMSC, divisor, 0, 0, NULL); sharedPixmap->timeline.point++; } else { xcb_present_pixmap(pwin->inst->conn, pwin->xwin, sharedPixmap->xpix, pwin->last_present_serial, 0, 0, // No update regions 0, 0, // No offset -- update the whole window 0, 0, 0, // No CRTC or fences options, targetMSC, divisor, 0, 0, NULL); } xcb_flush(pwin->inst->conn); sharedPixmap->status = BUFFER_STATUS_IN_USE; sharedPixmap->last_present_serial = pwin->last_present_serial; } /** * Allocates a shared Pixmap for a color buffer. */ static EGLBoolean CreateSharedPixmap(EplSurface *psurf, X11ColorBuffer *buffer, const EplFormatInfo *fmt) { X11Window *pwin = (X11Window *) psurf->priv; xcb_void_cookie_t cookie; xcb_generic_error_t *error; int fd = -1; assert(buffer->xpix == 0); // Note that XCB will close the file descriptor after it sends the request, // so even if we already have a file descriptor, we have to duplicate it. if (buffer->fd >= 0) { fd = dup(buffer->fd); } else { fd = gbm_bo_get_fd(buffer->gbo); } if (fd < 0) { return EGL_FALSE; } if (pwin->use_explicit_sync && buffer->timeline.xid == 0) { // If we're able to use explicit sync, then create a timeline object. if (!eplX11TimelineInit(pwin->inst, &buffer->timeline)) { close(fd); return EGL_FALSE; } } // Temporary hack: Send the PixmapFromBuffers request synchronously to // check for errors. buffer->xpix = xcb_generate_id(pwin->inst->conn); cookie = xcb_dri3_pixmap_from_buffers_checked(pwin->inst->conn, buffer->xpix, pwin->inst->xscreen->root, 1, gbm_bo_get_width(buffer->gbo), gbm_bo_get_height(buffer->gbo), gbm_bo_get_stride(buffer->gbo), gbm_bo_get_offset(buffer->gbo, 0), 0, 0, 0, 0, 0, 0, eplFormatInfoDepth(fmt), fmt->bpp, gbm_bo_get_modifier(buffer->gbo), &fd); error = xcb_request_check(pwin->inst->conn, cookie); if (error != NULL) { buffer->xpix = 0; free(error); return EGL_FALSE; } return EGL_TRUE; } static void WindowDamageCallback(void *param, int syncfd, unsigned int flags) { EplSurface *surf = param; X11Window *pwin = (X11Window *) surf->priv; X11ColorBuffer *sharedPixmap = NULL; pthread_mutex_lock(&pwin->mutex); if (pwin->skip_update_callback) { // If we're in the middle of an eglSwapBuffers or teardown, then // don't bother doing anything here. goto done; } // Check for any pending events first. PollForWindowEvents(surf); if (pwin->native_destroyed || surf->deleted) { goto done; } if (pwin->prime) { sharedPixmap = pwin->current_prime; } else { sharedPixmap = pwin->current_front; } assert(sharedPixmap != NULL); if (sharedPixmap->xpix == 0) { if (!CreateSharedPixmap(surf, sharedPixmap, pwin->format->fmt)) { goto done; } } if (pwin->use_explicit_sync) { EGLBoolean syncOK = EGL_FALSE; if (syncfd >= 0) { if (eplX11TimelineAttachSyncFD(pwin->inst, &sharedPixmap->timeline, syncfd)) { syncOK = EGL_TRUE; } } if (!syncOK) { uint32_t handle; uint64_t point; if (!eplX11WaitForFD(syncfd)) { goto done; } // If eplX11TimelineAttachSyncFD fails or if we don't have a // sync FD, then just manually signal the next timeline point. handle = sharedPixmap->timeline.handle; point = sharedPixmap->timeline.point + 1; if (pwin->inst->platform->priv->drm.SyncobjTimelineSignal( gbm_device_get_fd(pwin->inst->gbmdev), &handle, &point, 1) != 0) { goto done; } sharedPixmap->timeline.point++; } } else { // If we don't have explicit sync, then just do a CPU wait. // TODO: Is there a way that we can reliably use implicit sync if // the server supports it? if (!eplX11WaitForFD(syncfd)) { goto done; } } SendPresentPixmap(surf, sharedPixmap, XCB_PRESENT_OPTION_ASYNC | XCB_PRESENT_OPTION_COPY); done: pthread_mutex_unlock(&pwin->mutex); } static EGLBoolean CheckExistingWindow(EplDisplay *pdpy, xcb_window_t xwin) { EplSurface *psurf; glvnd_list_for_each_entry(psurf, &pdpy->surface_list, entry) { if (psurf->type == EPL_SURFACE_TYPE_WINDOW) { X11Window *pwin = (X11Window *) psurf->priv; if (pwin->xwin == xwin) { eplSetError(pdpy->platform, EGL_BAD_ALLOC, "An EGLSurface already exists for window 0x%x\n", xwin); return EGL_FALSE; } } } return EGL_TRUE; } EGLSurface eplX11CreateWindowSurface(EplPlatformData *plat, EplDisplay *pdpy, EplSurface *surf, EGLConfig config, void *native_surface, const EGLAttrib *attribs, EGLBoolean create_platform) { X11DisplayInstance *inst = pdpy->priv->inst; xcb_window_t xwin = eplX11GetNativeXID(pdpy, native_surface, create_platform); xcb_void_cookie_t presentSelectCookie; xcb_get_window_attributes_cookie_t winodwAttribCookie; xcb_get_window_attributes_reply_t *windowAttribReply = NULL; xcb_present_query_capabilities_cookie_t presentCapsCookie; xcb_present_query_capabilities_reply_t *presentCapsReply = NULL; xcb_get_geometry_cookie_t geomCookie; xcb_get_geometry_reply_t *geomReply = NULL; xcb_generic_error_t *error = NULL; X11Window *pwin = NULL; EGLSurface esurf = EGL_NO_SURFACE; const EplConfig *configInfo; const X11DriverFormat *fmt; uint64_t *mods = NULL; int numMods = 0; EGLBoolean prime = EGL_FALSE; EGLAttrib platformAttribs[15]; EGLAttrib *internalAttribs = NULL; uint32_t eventMask; if (xwin == 0) { eplSetError(plat, EGL_BAD_NATIVE_WINDOW, "Invalid native window %p\n", native_surface); return EGL_NO_SURFACE; } if (!CheckExistingWindow(pdpy, xwin)) { return EGL_NO_SURFACE; } configInfo = eplConfigListFind(inst->configs, config); if (configInfo == NULL) { eplSetError(plat, EGL_BAD_CONFIG, "Invalid EGLConfig %p", config); return EGL_NO_SURFACE; } if (!(configInfo->surfaceMask & EGL_WINDOW_BIT)) { eplSetError(plat, EGL_BAD_CONFIG, "EGLConfig %p does not support windows", config); return EGL_NO_SURFACE; } internalAttribs = eplX11GetInternalSurfaceAttribs(plat, pdpy, internalAttribs); if (internalAttribs == NULL) { goto done; } fmt = eplX11FindDriverFormat(inst, configInfo->fourcc); assert(fmt != NULL); pwin = calloc(1, sizeof(X11Window)); if (pwin == NULL) { eplSetError(plat, EGL_BAD_ALLOC, "Out of memory"); goto done; } if (!eplInitRecursiveMutex(&pwin->mutex)) { eplSetError(plat, EGL_BAD_ALLOC, "Can't allocate internal mutex"); free(pwin); pwin = NULL; goto done; } glvnd_list_init(&pwin->color_buffers); glvnd_list_init(&pwin->prime_buffers); surf->priv = (EplImplSurface *) pwin; pwin->inst = eplX11DisplayInstanceRef(inst); pwin->xwin = xwin; pwin->format = fmt; pwin->modifier = DRM_FORMAT_MOD_INVALID; pwin->swap_interval = 1; if (!FindSupportedModifiers(inst, fmt, xwin, &mods, &numMods, &prime)) { eplSetError(plat, EGL_BAD_CONFIG, "No matching format modifiers for window"); goto done; } presentCapsCookie = xcb_present_query_capabilities(inst->conn, xwin); presentCapsReply = xcb_present_query_capabilities_reply(inst->conn, presentCapsCookie, &error); if (presentCapsReply == NULL) { eplSetError(plat, EGL_BAD_NATIVE_WINDOW, "Failed to query present capabilities for window 0x%x", xwin); goto done; } pwin->present_capabilities = presentCapsReply->capabilities; if ((pwin->present_capabilities & XCB_PRESENT_CAPABILITY_SYNCOBJ) && inst->supports_explicit_sync) { pwin->use_explicit_sync = EGL_TRUE; } /* * Send the PresentSelectInput event first. If we sent the * XGetWindowAttributes request first, then it would be possible for the * window to be resized after the XGetWindowAttributes and before the * PresentSelectInput, and we wouldn't see the new size. * * Note that if we have explicit sync support, then we'll wait on the * timeline sync objects to know when a buffer frees up. Otherwise, we need * to keep track of PresentIdleNotify events. */ eventMask = XCB_PRESENT_EVENT_MASK_CONFIGURE_NOTIFY | XCB_PRESENT_EVENT_MASK_COMPLETE_NOTIFY; if (!pwin->use_explicit_sync) { eventMask |= XCB_PRESENT_EVENT_MASK_IDLE_NOTIFY; } pwin->present_event_id = xcb_generate_id(inst->conn); pwin->present_event = xcb_register_for_special_xge(inst->conn, &xcb_present_id, pwin->present_event_id, &pwin->present_event_stamp); presentSelectCookie = xcb_present_select_input_checked(inst->conn, pwin->present_event_id, xwin, eventMask); error = xcb_request_check(inst->conn, presentSelectCookie); if (error != NULL) { eplSetError(plat, EGL_BAD_NATIVE_WINDOW, "Invalid window 0x%x", xwin); goto done; } winodwAttribCookie = xcb_get_window_attributes(inst->conn, xwin); windowAttribReply = xcb_get_window_attributes_reply(inst->conn, winodwAttribCookie, &error); if (windowAttribReply == NULL) { eplSetError(plat, EGL_BAD_NATIVE_WINDOW, "Invalid window 0x%x", xwin); goto done; } if ((uint32_t) configInfo->nativeVisualID != windowAttribReply->visual) { eplSetError(plat, EGL_BAD_CONFIG, "EGLConfig %p uses X visual 0x%x, but window 0x%x uses visual 0x%x", config, configInfo->nativeVisualID, xwin, windowAttribReply->visual); goto done; } geomCookie = xcb_get_geometry(inst->conn, xwin); geomReply = xcb_get_geometry_reply(inst->conn, geomCookie, &error); if (geomReply == NULL) { eplSetError(plat, EGL_BAD_NATIVE_WINDOW, "Invalid window 0x%x", xwin); goto done; } if (geomReply->root != inst->xscreen->root) { eplSetError(plat, EGL_BAD_NATIVE_WINDOW, "Window 0x%x is on the wrong screen", xwin); goto done; } pwin->pending_width = geomReply->width; pwin->pending_height = geomReply->height; if (!AllocWindowBuffers(surf, mods, numMods, prime)) { eplSetError(plat, EGL_BAD_ALLOC, "Can't allocate color buffers"); goto done; } platformAttribs[0] = GL_FRONT; platformAttribs[1] = (EGLAttrib) pwin->current_front->buffer; platformAttribs[2] = GL_BACK; platformAttribs[3] = (EGLAttrib) pwin->current_back->buffer; platformAttribs[4] = EGL_PLATFORM_SURFACE_BLIT_TARGET_NVX; if (pwin->current_prime != NULL) { platformAttribs[5] = (EGLAttrib) pwin->current_prime->buffer; } else { platformAttribs[5] = (EGLAttrib) NULL; } platformAttribs[6] = EGL_PLATFORM_SURFACE_UPDATE_CALLBACK_NVX; platformAttribs[7] = (EGLAttrib) WindowUpdateCallback; platformAttribs[8] = EGL_PLATFORM_SURFACE_UPDATE_CALLBACK_PARAM_NVX; platformAttribs[9] = (EGLAttrib) surf; platformAttribs[10] = EGL_PLATFORM_SURFACE_DAMAGE_CALLBACK_NVX; platformAttribs[11] = (EGLAttrib) WindowDamageCallback; platformAttribs[12] = EGL_PLATFORM_SURFACE_DAMAGE_CALLBACK_PARAM_NVX; platformAttribs[13] = (EGLAttrib) surf; platformAttribs[14] = EGL_NONE; esurf = inst->platform->priv->egl.PlatformCreateSurfaceNVX(inst->internal_display->edpy, config, platformAttribs, internalAttribs); done: if (esurf == EGL_NO_SURFACE) { eplX11FreeWindow(surf); } free(windowAttribReply); free(geomReply); free(presentCapsReply); free(error); free(mods); free(internalAttribs); return esurf; } void eplX11DestroyWindow(EplSurface *surf) { X11Window *pwin = (X11Window *) surf->priv; EGLSurface internalSurf; assert(surf->type == EPL_SURFACE_TYPE_WINDOW); /* * Lock the surface and increment skip_update_callback. After that, if * another thread tries to call the update callback after this, then * the update callback won't try to call into the driver. */ pthread_mutex_lock(&pwin->mutex); pwin->skip_update_callback++; internalSurf = surf->internal_surface; pthread_mutex_unlock(&pwin->mutex); /* * We have to unlock the surface before we call the driver's * eglDestroySurface. * * If another thread tries to call the window update callback for this * surface, then it will be holding a mutex in the driver, and then the * callback will try to lock the surface's mutex. * * If we had the surface locked here, then the driver's eglDestroySurface * implementation would try to take the driver's mutex, which would lead to * a deadlock. */ if (internalSurf != EGL_NO_SURFACE) { pwin->inst->platform->egl.DestroySurface(pwin->inst->internal_display->edpy, internalSurf); } } /** * Waits for at least one Present event to arrive. * * This function will unlock the surface and the display while waiting, so the * caller must check the EplSurface::deleted flag to check whether another * thread destroyed the surface in the meantime. */ static EGLBoolean WaitForWindowEvents(EplDisplay *pdpy, EplSurface *surf) { X11Window *pwin = (X11Window *) surf->priv; xcb_generic_event_t *xcbevt; /* * We don't want to block other threads while we wait, so we need to * release our locks. * * The use counter in EplDisplay should prevent the display from * getting terminated out from under us, and the refcount in EplSurface * will ensure that the EplSurface struct itself sticks around. * * It's still possible that the surface will get destroyed by another * thread in the meantime, though, so the caller needs to check for that. */ if (pwin->native_destroyed) { /* * If the X window was destroyed, then xcb_wait_for_special_event would * hang because it would never receive any events for that window. * However, a new enough X server will send a PresentConfigureNotify * event with a special flag set to notify the client when that * happens. * * Note that even though we unlock the window's mutex before calling * xcb_wait_for_special_event, we should still be safe from race * conditions. This function is only called from eglSwapBuffers, which * means that the window must be current. Thus, only one thread will * ever be here for this window. * * There is still a race condition if the X window gets destroyed * before the server receives the PresentPixmap request, but there's * not much that we can do about that. */ return EGL_TRUE; } pthread_mutex_unlock(&pwin->mutex); eplDisplayUnlock(pdpy); xcbevt = xcb_wait_for_special_event(pwin->inst->conn, pwin->present_event); eplDisplayLock(pdpy); pthread_mutex_lock(&pwin->mutex); // Sanity check: If something called eglTerminate, then that should have // destroyed the surface. assert(pdpy->priv->inst == pwin->inst || surf->deleted); if (surf->deleted) { /* * Some other thread came along and called eglDestroySurface or * eglTerminate. */ return EGL_TRUE; } if (xcbevt == NULL) { // Note that this only happens if the X11 connection gets killed, as // per XKillClient. eplSetError(pwin->inst->platform, EGL_BAD_ALLOC, "Failed to check window-system events."); pwin->native_destroyed = EGL_TRUE; return EGL_FALSE; } HandlePresentEvent(surf, xcbevt); free(xcbevt); // There might be more than one event, so poll, but don't wait for them. PollForWindowEvents(surf); return EGL_TRUE; } /** * Flush the command stream, and set up synchronization. * * If explicit sync is supported, then this will set up the next acquire point. * * Otherwise, this will fall back to using implicit sync if it's available, or * a simple glFinish if it's not. * * \param surf The window surface * \param buffer The shared buffer (either a render or a pitch linear buffer) * \return EGL_TRUE on success, or EGL_FALSE on failure. */ static EGLBoolean SyncRendering(EplDisplay *pdpy, EplSurface *surf, X11ColorBuffer *buffer) { X11Window *pwin = (X11Window *) surf->priv; int syncFd = -1; EGLSync sync = EGL_NO_SYNC; EGLBoolean success = EGL_FALSE; if (!pwin->inst->supports_EGL_ANDROID_native_fence_sync) { // If we don't have EGL_ANDROID_native_fence_sync, then we can't do // anything other than a glFinish here. assert(!pwin->use_explicit_sync); pwin->inst->platform->priv->egl.Finish(); return EGL_TRUE; } pwin->inst->platform->priv->egl.Flush(); sync = pwin->inst->platform->priv->egl.CreateSync(pwin->inst->internal_display->edpy, EGL_SYNC_NATIVE_FENCE_ANDROID, NULL); if (sync == EGL_NO_SYNC) { goto done; } syncFd = pwin->inst->platform->priv->egl.DupNativeFenceFDANDROID(pwin->inst->internal_display->edpy, sync); if (syncFd < 0) { goto done; } if (pwin->use_explicit_sync) { // If we support explicit sync, then always use that. if (eplX11TimelineAttachSyncFD(pwin->inst, &buffer->timeline, syncFd)) { success = EGL_TRUE; } else { /* * In theory, if eplX11TimelineAttachSyncFD fails, then we could * fall back here to a simple glFinish and then send an * already-signaled acquire point with the PresentPixmapSynced * request. * * For now, though, just fail eglSwapBuffers in this case. */ eplSetError(pwin->inst->platform, EGL_BAD_ALLOC, "Failed to attach timeline point"); } } else if (eplX11ImportDmaBufSyncFile(pwin->inst, buffer->fd, syncFd)) { success = EGL_TRUE; } else { pwin->inst->platform->priv->egl.Finish(); success = EGL_TRUE; } done: if (sync != EGL_NO_SYNC) { pwin->inst->platform->priv->egl.DestroySync(pwin->inst->internal_display->edpy, sync); } if (syncFd >= 0) { close(syncFd); } return success; } /** * Waits for a sync FD using eglWaitSync. * * Using eglWaitSync means that the GPU will wait for the fence, without * doing a CPU stall. * * \param inst The X11DisplayInstance * \param syncfd The sync file descriptor. This must be a regular fence. */ static EGLBoolean WaitForSyncFDGPU(X11DisplayInstance *inst, int syncfd) { EGLBoolean success = EGL_FALSE; if (syncfd >= 0) { const EGLAttrib syncAttribs[] = { EGL_SYNC_NATIVE_FENCE_FD_ANDROID, syncfd, EGL_NONE }; EGLSync sync = inst->platform->priv->egl.CreateSync(inst->internal_display->edpy, EGL_SYNC_NATIVE_FENCE_ANDROID, syncAttribs); if (sync != EGL_NO_SYNC) { success = inst->platform->priv->egl.WaitSync(inst->internal_display->edpy, sync, 0); inst->platform->priv->egl.DestroySync(inst->internal_display->edpy, sync); } } return success; } static EGLBoolean WaitImplicitFence(EplDisplay *pdpy, X11ColorBuffer *buffer) { EGLBoolean success = EGL_FALSE; int fd = -1; assert(pdpy->priv->inst->supports_implicit_sync); fd = eplX11ExportDmaBufSyncFile(pdpy->priv->inst, buffer->fd); if (fd >= 0) { success = WaitForSyncFDGPU(pdpy->priv->inst, fd); close(fd); } if (success) { buffer->status = BUFFER_STATUS_IDLE; } return success; } /** * Waits or polls for a buffer to free up, using implicit sync. * * Note that we can only wait for a buffer if we've received a * PresentIdleNotify event. If no buffers were ready, then the caller * has to wait for a Present event and try again. * * \param pdpy The EplDisplay pointer. * \param surf The EplSurface pointer. * \param buffer_list The list of buffers to check. * \param skip If not NULL, then ignore this buffer when checking the rest. * \param timeout_ms The number of milliseconds to wait. Zero to poll without blocking. * * \return The number of buffers that were checked, or -1 on error. */ static int CheckBufferReleaseImplicit(EplDisplay *pdpy, EplSurface *surf, struct glvnd_list *buffer_list, X11ColorBuffer *skip, int timeout_ms) { X11Window *pwin = (X11Window *) surf->priv; X11ColorBuffer *buffer; X11ColorBuffer **buffers; struct pollfd *fds; int count; int i; int ret, err; PollForWindowEvents(surf); count = 0; glvnd_list_for_each_entry(buffer, buffer_list, entry) { if (buffer != skip && buffer->status == BUFFER_STATUS_IDLE_NOTIFIED) { /* * If possible, extract a syncfd and wait on it using eglWaitSync, * instead of doing a CPU wait. */ if (WaitImplicitFence(pdpy, buffer)) { assert(buffer->status == BUFFER_STATUS_IDLE); return 1; } count++; } } if (count == 0) { return 0; } buffers = alloca(count * sizeof(X11ColorBuffer *)); fds = alloca(count * sizeof(struct pollfd)); count = 0; glvnd_list_for_each_entry(buffer, buffer_list, entry) { if (buffer != skip && buffer->status == BUFFER_STATUS_IDLE_NOTIFIED) { buffers[count] = buffer; fds[count].fd = buffer->fd; fds[count].events = POLLOUT; fds[count].revents = 0; count++; } } // Release the locks while we wait, so that we don't block // other threads. pthread_mutex_unlock(&pwin->mutex); eplDisplayUnlock(pdpy); ret = poll(fds, count, timeout_ms); err = errno; eplDisplayLock(pdpy); pthread_mutex_lock(&pwin->mutex); if (surf->deleted) { /* * Some other thread came along and called * eglDestroySurface or eglTerminate. */ return count; } else if (ret > 0) { for (i=0; istatus = BUFFER_STATUS_IDLE; } } return count; } else if (ret == 0 || err == ETIME || err == EINTR) { // Nothing freed up before the timeout, but that's not a fatal error // here. return count; } else { eplSetError(pwin->inst->platform, EGL_BAD_ALLOC, "Internal error: poll() failed: %s\n", strerror(err)); return -1; } } /** * Checks for a free buffer, without any sort of server -> client sync. * * Without any server -> client synchronization, the best we can do is to * wait for a PresentIdleNotify event, and then hope that the buffer really is * idle by the time we start rendering to it again. */ static int CheckBufferReleaseNoSync(EplDisplay *pdpy, EplSurface *surf, struct glvnd_list *buffer_list, X11ColorBuffer *skip) { X11ColorBuffer *buffer; int numPending = 0; PollForWindowEvents(surf); glvnd_list_for_each_entry(buffer, buffer_list, entry) { if (buffer != skip && buffer->status == BUFFER_STATUS_IDLE_NOTIFIED) { buffer->status = BUFFER_STATUS_IDLE; numPending++; } } return numPending; } /** * Waits for a timeline point. * * This will attempt to use eglWaitSync to let the GPU wait on the sync point, * but if that fails, then it'll fall back to a CPU wait. */ static EGLBoolean WaitTimelinePoint(X11DisplayInstance *inst, X11Timeline *timeline) { int syncfd = eplX11TimelinePointToSyncFD(inst, timeline); EGLBoolean success = EGL_FALSE; if (syncfd >= 0) { success = WaitForSyncFDGPU(inst, syncfd); } if (!success) { // If using eglWaitSync failed, then just do a CPU wait on the timeline // point. uint32_t first; if (inst->platform->priv->drm.SyncobjTimelineWait( gbm_device_get_fd(inst->gbmdev), &timeline->handle, &timeline->point, 1, INT64_MAX, DRM_SYNCOBJ_WAIT_FLAGS_WAIT_FOR_SUBMIT, &first) == 0) { eplSetError(inst->platform, EGL_BAD_ALLOC, "Internal error: drmSyncobjTimelineWait(WAIT_FOR_SUBMIT) failed: %s\n", strerror(errno)); success = EGL_TRUE; } } return success; } /** * Waits or polls for a buffer to free up, using explicit sync. * * Unlike implicit sync, we don't need to wait for a PresentIdleNotify event * before waiting on a buffer. * * \param pdpy The EplDisplay pointer. * \param surf The EplSurface pointer. * \param buffer_list The list of buffers to check. * \param skip If not NULL, then ignore this buffer when checking the rest. * \param timeout_ms The number of milliseconds to wait. Zero to poll without blocking. * * \return The number of buffers that were checked, or -1 on error. */ static int CheckBufferReleaseExplicit(EplDisplay *pdpy, EplSurface *surf, struct glvnd_list *buffer_list, X11ColorBuffer *skip, int timeout_ms) { X11Window *pwin = (X11Window *) surf->priv; X11ColorBuffer *buffer; X11ColorBuffer **buffers; uint32_t *handles; uint64_t *points; int64_t timeout; uint32_t count; uint32_t first; int ret, err; count = 0; glvnd_list_for_each_entry(buffer, buffer_list, entry) { if (buffer != skip && buffer->status != BUFFER_STATUS_IDLE) { count++; } } if (count == 0) { return 0; } buffers = alloca(count * sizeof(X11ColorBuffer *)); handles = alloca(count * sizeof(uint32_t)); points = alloca(count * sizeof(uint64_t)); count = 0; glvnd_list_for_each_entry(buffer, buffer_list, entry) { if (buffer != skip && buffer->status != BUFFER_STATUS_IDLE) { buffers[count] = buffer; handles[count] = buffer->timeline.handle; points[count] = buffer->timeline.point; count++; } } if (timeout_ms > 0) { struct timespec ts; clock_gettime(CLOCK_MONOTONIC, &ts); timeout = ((int64_t) ts.tv_sec) * 1000000000 + ts.tv_nsec; timeout += timeout_ms * 1000000; } else { timeout = 0; } // Release the locks while we wait, so that we don't block // other threads. pthread_mutex_unlock(&pwin->mutex); eplDisplayUnlock(pdpy); ret = pwin->inst->platform->priv->drm.SyncobjTimelineWait( gbm_device_get_fd(pwin->inst->gbmdev), handles, points, count, timeout, DRM_SYNCOBJ_WAIT_FLAGS_WAIT_AVAILABLE, &first); err = errno; eplDisplayLock(pdpy); pthread_mutex_lock(&pwin->mutex); if (surf->deleted) { /* * Some other thread came along and called * eglDestroySurface or eglTerminate. */ return count; } else if (ret == 0) { assert(first < count); if (WaitTimelinePoint(pwin->inst, &buffers[first]->timeline)) { buffers[first]->status = BUFFER_STATUS_IDLE; return count; } else { return -1; } } else if (err == ETIME || err == EINTR) { // Nothing freed up before the timeout, but that's not a fatal error // here. return count; } else { eplSetError(pwin->inst->platform, EGL_BAD_ALLOC, "Internal error: drmSyncobjTimelineWait(WAIT_AVAILABLE) failed: %s\n", strerror(err)); return -1; } } /** * Returns a free buffer. * * \param pdpy The EplDisplay pointer * \param surf The EplSurface pointer * \param skip If non-NULL, then ignore this buffer even if it's free. * \param prime If true, then look for a free PRIME buffer. Otherwise, look for * a regular color buffer. * \return A free X11ColorBuffer, or NULL on failure. This will also return * NULL if the EGLSurface or the native window is destroyed. */ static X11ColorBuffer *GetFreeBuffer(EplDisplay *pdpy, EplSurface *surf, X11ColorBuffer *skip, EGLBoolean prime) { X11Window *pwin = (X11Window *) surf->priv; struct glvnd_list *buffers; int maxBuffers; if (prime) { assert(pwin->prime); buffers = &pwin->prime_buffers; maxBuffers = MAX_PRIME_BUFFERS; } else { buffers = &pwin->color_buffers; maxBuffers = MAX_COLOR_BUFFERS; } /* * First, poll to see if any buffers have already freed up. Do this up * front so that we don't try to allocate a new buffer unnecessarily. */ if (pwin->use_explicit_sync) { if (CheckBufferReleaseExplicit(pdpy, surf, buffers, skip, 0) < 0) { return NULL; } } else if (pwin->inst->supports_implicit_sync) { if (CheckBufferReleaseImplicit(pdpy, surf, buffers, skip, 0) < 0) { return NULL; } } else { if (CheckBufferReleaseNoSync(pdpy, surf, buffers, skip) < 0) { return NULL; } } while (!surf->deleted && !pwin->native_destroyed) { X11ColorBuffer *buffer = NULL; int numBuffers = 0; // Look to see if a buffer is already free. glvnd_list_for_each_entry(buffer, buffers, entry) { if (buffer->status == BUFFER_STATUS_IDLE && buffer != skip) { return buffer; } numBuffers++; } if (numBuffers < maxBuffers) { // We didn't find a free buffer, but we don't have our maximum // number of buffers yet, so allocate a new one. if (prime) { buffer = AllocatePrimeBuffer(pwin->inst, pwin->format->fourcc, pwin->width, pwin->height); } else { buffer = AllocOneColorBuffer(pwin->inst, pwin->format->fmt, pwin->width, pwin->height, &pwin->modifier, 1, !pwin->prime); } if (buffer == NULL) { return NULL; } glvnd_list_add(&buffer->entry, buffers); return buffer; } // Otherwise, we have to wait for a buffer to free up. if (pwin->use_explicit_sync) { /* * With explicit sync, we can just wait on every buffer, even if we * haven't gotten a PresentIdleNotify event. * * We do still poll for window events, though, in case the native * window gets destroyed while we're waiting. */ if (CheckBufferReleaseExplicit(pdpy, surf, buffers, skip, RELEASE_WAIT_TIMEOUT) <= 0) { return NULL; } PollForWindowEvents(surf); } else { int numChecked; if (pwin->inst->supports_implicit_sync) { numChecked = CheckBufferReleaseImplicit(pdpy, surf, buffers, skip, RELEASE_WAIT_TIMEOUT); } else { numChecked = CheckBufferReleaseNoSync(pdpy, surf, buffers, skip); } if (numChecked < 0) { return NULL; } else if (numChecked == 0) { /* * There weren't any buffers to wait on yet, so wait for a * present event. * * If we receieve a PresentIdleNotify event, then HandlePresentEvent * will mark the corresponding buffer as ready to wait on, and then * CheckBufferReleaseImplicit/NoSync will find it on the next pass * through this loop. */ if (!WaitForWindowEvents(pdpy, surf)) { return NULL; } } } } return NULL; } static EGLBoolean CheckWindowDeleted(EplSurface *surf, EGLBoolean *ret_success) { X11Window *pwin = (X11Window *) surf->priv; if (surf->deleted) { *ret_success = EGL_TRUE; return EGL_TRUE; } else if (pwin->native_destroyed) { *ret_success = EGL_FALSE; eplSetError(pwin->inst->platform, EGL_BAD_NATIVE_WINDOW, "The X11 window has been destroyed"); return EGL_TRUE; } return EGL_FALSE; } EGLBoolean eplX11SwapBuffers(EplPlatformData *plat, EplDisplay *pdpy, EplSurface *surf, const EGLint *rects, EGLint n_rects) { X11Window *pwin = (X11Window *) surf->priv; X11ColorBuffer *sharedPixmap = NULL; uint32_t options = 0; EGLBoolean resized = EGL_FALSE; EGLBoolean ret = EGL_FALSE; pthread_mutex_lock(&pwin->mutex); // Disable the update callback, so that we don't have to worry about it // reallocating the color buffers while we're trying to rearrange them. pwin->skip_update_callback++; if (CheckWindowDeleted(surf, &ret)) { goto done; } if (pwin->prime) { sharedPixmap = GetFreeBuffer(pdpy, surf, NULL, EGL_TRUE); if (CheckWindowDeleted(surf, &ret)) { goto done; } if (sharedPixmap == NULL) { goto done; } // Blit from the current back buffer to the shared linear buffer. if (!pwin->inst->platform->priv->egl.PlatformCopyColorBufferNVX(pwin->inst->internal_display->edpy, pwin->current_back->buffer, sharedPixmap->buffer)) { eplSetError(plat, EGL_BAD_ALLOC, "Failed to blit back buffer"); goto done; } } else { // For normal rendering, we share the back buffer directly with the // server. sharedPixmap = pwin->current_back; } if (sharedPixmap->xpix == 0) { if (!CreateSharedPixmap(surf, sharedPixmap, pwin->format->fmt)) { eplSetError(plat, EGL_BAD_ALLOC, "Can't create shared pixmap"); goto done; } } // Sanity check: We shouldn't have been rendering to a buffer while it's in // use in the server. assert(sharedPixmap->status == BUFFER_STATUS_IDLE); if (!SyncRendering(pdpy, surf, sharedPixmap)) { goto done; } if (!pwin->inst->force_prime) { // If we're always using PRIME, then the shared pixmap will always be // DRM_FORMAT_MOD_LINEAR, so it doesn't matter whether that's optimal // or not. options |= XCB_PRESENT_OPTION_SUBOPTIMAL; } // Wait for pending frames to complete before we continue. while (1) { uint32_t pending = pwin->last_present_serial - pwin->last_complete_serial; if (pending <= MAX_PENDING_FRAMES) { break; } if (!WaitForWindowEvents(pdpy, surf)) { goto done; } if (CheckWindowDeleted(surf, &ret)) { goto done; } } SendPresentPixmap(surf, sharedPixmap, options); /* * Check if we need to reallocate the buffers to deal with a resize or new * format modifiers. * * If we have to reallocate the buffer, then AllocWindowBuffers will * attach new front and back buffers. */ if (!CheckReallocWindow(surf, EGL_TRUE, &resized)) { eplSetError(plat, EGL_BAD_ALLOC, "Failed to allocate resized buffers."); goto done; } if (!resized) { X11ColorBuffer *newBack = NULL; EGLAttrib buffers[] = { GL_BACK, 0, EGL_PLATFORM_SURFACE_BLIT_TARGET_NVX, 0, GL_FRONT, (EGLAttrib) pwin->current_back->buffer, EGL_NONE }; if (pwin->prime) { // For PRIME, the server gets the linear buffer, so we can just swap // the front and back buffers. newBack = pwin->current_front; } else { newBack = GetFreeBuffer(pdpy, surf, pwin->current_back, EGL_FALSE); if (CheckWindowDeleted(surf, &ret)) { goto done; } if (newBack == NULL) { goto done; } } buffers[1] = (EGLAttrib) newBack->buffer; pwin->current_front = pwin->current_back; pwin->current_back = newBack; if (pwin->prime) { pwin->current_prime = sharedPixmap; buffers[3] = (EGLAttrib) sharedPixmap->buffer; } ret = pwin->inst->platform->priv->egl.PlatformSetColorBuffersNVX(pwin->inst->internal_display->edpy, surf->internal_surface, buffers); if (!ret) { // Note: This should never fail. The drawable doesn't change size // here, so the driver doesn't need to reallocate anything. eplSetError(plat, EGL_BAD_ALLOC, "Driver error: Can't assign new color buffers"); goto done; } } ret = EGL_TRUE; assert(pwin->current_back->status == BUFFER_STATUS_IDLE); done: pwin->skip_update_callback--; pthread_mutex_unlock(&pwin->mutex); return ret; } EGLBoolean eplX11SwapInterval(EGLDisplay edpy, EGLint interval) { EplDisplay *pdpy = eplDisplayAcquire(edpy); EGLSurface esurf; EGLBoolean ret = EGL_FALSE; if (pdpy == NULL) { return EGL_FALSE; } esurf = pdpy->platform->egl.GetCurrentSurface(EGL_DRAW); if (esurf != EGL_NO_SURFACE) { EplSurface *psurf = eplSurfaceAcquire(pdpy, esurf); if (psurf != NULL) { if (psurf->type == EPL_SURFACE_TYPE_WINDOW) { X11Window *pwin = (X11Window *) psurf->priv; pwin->swap_interval = interval; if (pwin->swap_interval < 0) { pwin->swap_interval = 0; } } eplSurfaceRelease(pdpy, psurf); ret = EGL_TRUE; } else { // If we don't recognize he current EGLSurface, then just pass the // call through to the driver. ret = pdpy->platform->priv->egl.SwapInterval(edpy, interval); } } else { eplSetError(pdpy->platform, EGL_BAD_SURFACE, "eglSwapInterval called without a current EGLSurface"); } eplDisplayRelease(pdpy); return ret; } EGLBoolean eplX11WaitGLWindow(EplDisplay *pdpy, EplSurface *psurf) { X11Window *pwin = (X11Window *) psurf->priv; while ((pwin->last_present_serial - pwin->last_complete_serial) > 0 && !psurf->deleted && !pwin->native_destroyed) { if (!WaitForWindowEvents(pdpy, psurf)) { return EGL_FALSE; } } return EGL_TRUE; }