| /************************************************************************** |
| * |
| * Copyright (C) 2014 Red Hat Inc. |
| * |
| * 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 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. |
| * |
| **************************************************************************/ |
| /* create our own EGL offscreen rendering context via gbm and rendernodes */ |
| |
| |
| /* if we are using EGL and rendernodes then we talk via file descriptors to the remote |
| node */ |
| #ifdef HAVE_CONFIG_H |
| #include "config.h" |
| #endif |
| |
| #define EGL_EGLEXT_PROTOTYPES |
| #include <dirent.h> |
| #include <fcntl.h> |
| #include <unistd.h> |
| #include <stdio.h> |
| #include <string.h> |
| #include <stdlib.h> |
| #include <errno.h> |
| #include <stdbool.h> |
| #include <epoxy/egl.h> |
| #include <gbm.h> |
| #include <xf86drm.h> |
| #include "virglrenderer.h" |
| #include "virgl_egl.h" |
| |
| #include "virgl_hw.h" |
| struct virgl_egl { |
| int fd; |
| struct gbm_device *gbm_dev; |
| EGLDisplay egl_display; |
| EGLConfig egl_conf; |
| EGLContext egl_ctx; |
| bool have_mesa_drm_image; |
| bool have_mesa_dma_buf_img_export; |
| }; |
| |
| static int egl_rendernode_open(void) |
| { |
| DIR *dir; |
| struct dirent *e; |
| int r, fd; |
| char *p; |
| dir = opendir("/dev/dri"); |
| if (!dir) |
| return -1; |
| |
| fd = -1; |
| while ((e = readdir(dir))) { |
| if (e->d_type != DT_CHR) |
| continue; |
| |
| if (strncmp(e->d_name, "renderD", 7)) |
| continue; |
| |
| r = asprintf(&p, "/dev/dri/%s", e->d_name); |
| if (r < 0) |
| return -1; |
| |
| r = open(p, O_RDWR | O_CLOEXEC | O_NOCTTY | O_NONBLOCK); |
| if (r < 0){ |
| free(p); |
| continue; |
| } |
| fd = r; |
| free(p); |
| break; |
| } |
| |
| closedir(dir); |
| if (fd < 0) |
| return -1; |
| return fd; |
| } |
| |
| static bool virgl_egl_has_extension_in_string(const char *haystack, const char *needle) |
| { |
| const unsigned needle_len = strlen(needle); |
| |
| if (needle_len == 0) |
| return false; |
| |
| while (true) { |
| const char *const s = strstr(haystack, needle); |
| |
| if (s == NULL) |
| return false; |
| |
| if (s[needle_len] == ' ' || s[needle_len] == '\0') { |
| return true; |
| } |
| |
| /* strstr found an extension whose name begins with |
| * needle, but whose name is not equal to needle. |
| * Restart the search at s + needle_len so that we |
| * don't just find the same extension again and go |
| * into an infinite loop. |
| */ |
| haystack = s + needle_len; |
| } |
| |
| return false; |
| } |
| |
| struct virgl_egl *virgl_egl_init(void) |
| { |
| static const EGLint conf_att[] = { |
| EGL_SURFACE_TYPE, EGL_WINDOW_BIT, |
| EGL_RENDERABLE_TYPE, EGL_OPENGL_BIT, |
| EGL_RED_SIZE, 1, |
| EGL_GREEN_SIZE, 1, |
| EGL_BLUE_SIZE, 1, |
| EGL_ALPHA_SIZE, 0, |
| EGL_NONE, |
| }; |
| static const EGLint ctx_att[] = { |
| EGL_CONTEXT_CLIENT_VERSION, 2, |
| EGL_NONE |
| }; |
| EGLBoolean b; |
| EGLenum api; |
| EGLint major, minor, n; |
| const char *extension_list; |
| struct virgl_egl *d; |
| |
| d = malloc(sizeof(struct virgl_egl)); |
| if (!d) |
| return NULL; |
| |
| d->fd = egl_rendernode_open(); |
| if (d->fd == -1) |
| goto fail; |
| d->gbm_dev = gbm_create_device(d->fd); |
| if (!d->gbm_dev) |
| goto fail; |
| |
| const char *client_extensions = eglQueryString (NULL, EGL_EXTENSIONS); |
| |
| if (strstr (client_extensions, "EGL_KHR_platform_base")) { |
| PFNEGLGETPLATFORMDISPLAYEXTPROC get_platform_display = |
| (PFNEGLGETPLATFORMDISPLAYEXTPROC) eglGetProcAddress ("eglGetPlatformDisplay"); |
| |
| if (get_platform_display) |
| d->egl_display = get_platform_display (EGL_PLATFORM_GBM_KHR, |
| (EGLNativeDisplayType)d->gbm_dev, NULL); |
| } else if (strstr (client_extensions, "EGL_EXT_platform_base")) { |
| PFNEGLGETPLATFORMDISPLAYEXTPROC get_platform_display = |
| (PFNEGLGETPLATFORMDISPLAYEXTPROC) eglGetProcAddress ("eglGetPlatformDisplayEXT"); |
| |
| if (get_platform_display) |
| d->egl_display = get_platform_display (EGL_PLATFORM_GBM_KHR, |
| (EGLNativeDisplayType)d->gbm_dev, NULL); |
| } else { |
| d->egl_display = eglGetDisplay((EGLNativeDisplayType)d->gbm_dev); |
| } |
| |
| if (!d->egl_display) |
| goto fail; |
| |
| b = eglInitialize(d->egl_display, &major, &minor); |
| if (!b) |
| goto fail; |
| |
| extension_list = eglQueryString(d->egl_display, EGL_EXTENSIONS); |
| #ifdef VIRGL_EGL_DEBUG |
| fprintf(stderr, "EGL major/minor: %d.%d\n", major, minor); |
| fprintf(stderr, "EGL version: %s\n", |
| eglQueryString(d->egl_display, EGL_VERSION)); |
| fprintf(stderr, "EGL vendor: %s\n", |
| eglQueryString(d->egl_display, EGL_VENDOR)); |
| fprintf(stderr, "EGL extensions: %s\n", extension_list); |
| #endif |
| /* require surfaceless context */ |
| if (!virgl_egl_has_extension_in_string(extension_list, "EGL_KHR_surfaceless_context")) |
| goto fail; |
| |
| d->have_mesa_drm_image = false; |
| d->have_mesa_dma_buf_img_export = false; |
| if (virgl_egl_has_extension_in_string(extension_list, "EGL_MESA_drm_image")) |
| d->have_mesa_drm_image = true; |
| |
| if (virgl_egl_has_extension_in_string(extension_list, "EGL_MESA_image_dma_buf_export")) |
| d->have_mesa_dma_buf_img_export = true; |
| |
| if (d->have_mesa_drm_image == false && d->have_mesa_dma_buf_img_export == false) { |
| fprintf(stderr, "failed to find drm image extensions\n"); |
| goto fail; |
| } |
| |
| api = EGL_OPENGL_API; |
| b = eglBindAPI(api); |
| if (!b) |
| goto fail; |
| |
| b = eglChooseConfig(d->egl_display, conf_att, &d->egl_conf, |
| 1, &n); |
| |
| if (!b || n != 1) |
| goto fail; |
| |
| d->egl_ctx = eglCreateContext(d->egl_display, |
| d->egl_conf, |
| EGL_NO_CONTEXT, |
| ctx_att); |
| if (!d->egl_ctx) |
| goto fail; |
| |
| |
| eglMakeCurrent(d->egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE, |
| d->egl_ctx); |
| return d; |
| fail: |
| free(d); |
| return NULL; |
| } |
| |
| void virgl_egl_destroy(struct virgl_egl *d) |
| { |
| eglMakeCurrent(d->egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE, |
| EGL_NO_CONTEXT); |
| eglDestroyContext(d->egl_display, d->egl_ctx); |
| eglTerminate(d->egl_display); |
| gbm_device_destroy(d->gbm_dev); |
| close(d->fd); |
| free(d); |
| } |
| |
| virgl_renderer_gl_context virgl_egl_create_context(struct virgl_egl *ve, struct virgl_gl_ctx_param *vparams) |
| { |
| EGLContext eglctx; |
| EGLint ctx_att[] = { |
| EGL_CONTEXT_CLIENT_VERSION, vparams->major_ver, |
| EGL_CONTEXT_MINOR_VERSION_KHR, vparams->minor_ver, |
| EGL_NONE |
| }; |
| eglctx = eglCreateContext(ve->egl_display, |
| ve->egl_conf, |
| vparams->shared ? eglGetCurrentContext() : EGL_NO_CONTEXT, |
| ctx_att); |
| return (virgl_renderer_gl_context)eglctx; |
| } |
| |
| void virgl_egl_destroy_context(struct virgl_egl *ve, virgl_renderer_gl_context virglctx) |
| { |
| EGLContext eglctx = (EGLContext)virglctx; |
| eglDestroyContext(ve->egl_display, eglctx); |
| } |
| |
| int virgl_egl_make_context_current(struct virgl_egl *ve, virgl_renderer_gl_context virglctx) |
| { |
| EGLContext eglctx = (EGLContext)virglctx; |
| |
| return eglMakeCurrent(ve->egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE, |
| eglctx); |
| } |
| |
| virgl_renderer_gl_context virgl_egl_get_current_context(struct virgl_egl *ve) |
| { |
| EGLContext eglctx = eglGetCurrentContext(); |
| return (virgl_renderer_gl_context)eglctx; |
| } |
| |
| int virgl_egl_get_fourcc_for_texture(struct virgl_egl *ve, uint32_t tex_id, uint32_t format, int *fourcc) |
| { |
| int ret = EINVAL; |
| |
| #ifndef EGL_MESA_image_dma_buf_export |
| ret = 0; |
| goto fallback; |
| #else |
| EGLImageKHR image; |
| EGLBoolean b; |
| |
| if (!ve->have_mesa_dma_buf_img_export) |
| goto fallback; |
| |
| image = eglCreateImageKHR(ve->egl_display, eglGetCurrentContext(), EGL_GL_TEXTURE_2D_KHR, (EGLClientBuffer)(unsigned long)tex_id, NULL); |
| |
| if (!image) |
| return EINVAL; |
| |
| b = eglExportDMABUFImageQueryMESA(ve->egl_display, image, fourcc, NULL, NULL); |
| if (!b) |
| goto out_destroy; |
| ret = 0; |
| out_destroy: |
| eglDestroyImageKHR(ve->egl_display, image); |
| return ret; |
| |
| #endif |
| |
| fallback: |
| *fourcc = virgl_egl_get_gbm_format(format); |
| return ret; |
| } |
| |
| int virgl_egl_get_fd_for_texture(struct virgl_egl *ve, uint32_t tex_id, int *fd) |
| { |
| EGLImageKHR image; |
| EGLint stride; |
| #ifdef EGL_MESA_image_dma_buf_export |
| EGLint offset; |
| #endif |
| EGLBoolean b; |
| int ret; |
| image = eglCreateImageKHR(ve->egl_display, eglGetCurrentContext(), EGL_GL_TEXTURE_2D_KHR, (EGLClientBuffer)(unsigned long)tex_id, NULL); |
| |
| if (!image) |
| return EINVAL; |
| |
| ret = EINVAL; |
| if (ve->have_mesa_dma_buf_img_export) { |
| #ifdef EGL_MESA_image_dma_buf_export |
| b = eglExportDMABUFImageMESA(ve->egl_display, |
| image, |
| fd, |
| &stride, |
| &offset); |
| if (!b) |
| goto out_destroy; |
| #else |
| goto out_destroy; |
| #endif |
| } else { |
| #ifdef EGL_MESA_drm_image |
| EGLint handle; |
| int r; |
| b = eglExportDRMImageMESA(ve->egl_display, |
| image, |
| NULL, &handle, |
| &stride); |
| |
| if (!b) |
| goto out_destroy; |
| |
| fprintf(stderr,"image exported %d %d\n", handle, stride); |
| |
| r = drmPrimeHandleToFD(ve->fd, handle, DRM_CLOEXEC, fd); |
| if (r < 0) |
| goto out_destroy; |
| #else |
| goto out_destroy; |
| #endif |
| } |
| ret = 0; |
| out_destroy: |
| eglDestroyImageKHR(ve->egl_display, image); |
| return ret; |
| } |
| |
| uint32_t virgl_egl_get_gbm_format(uint32_t format) |
| { |
| switch (format) { |
| case VIRGL_FORMAT_B8G8R8X8_UNORM: |
| return GBM_FORMAT_XRGB8888; |
| case VIRGL_FORMAT_B8G8R8A8_UNORM: |
| return GBM_FORMAT_ARGB8888; |
| default: |
| fprintf(stderr, "unknown format to convert to GBM %d\n", format); |
| return 0; |
| } |
| } |