mirror of
https://github.com/qemu/qemu.git
synced 2024-11-28 22:33:36 +08:00
bc6a3565c8
This has the following visible changes: - GBM is required only for OpenGL dma-buf. - X11 is explicitly required by gtk-egl. - EGL is now mandatory for the OpenGL displays. The last one needs some detailed description. Before this change, EGL was tested only for OpenGL dma-buf with the check of EGL_MESA_image_dma_buf_export. However, all of the OpenGL displays depend on EGL and EGL_MESA_image_dma_buf_export is always defined by epoxy's EGL interface. Therefore, it makes more sense to always check the presence of EGL and say the OpenGL displays are available along with OpenGL dma-buf if it is present. Signed-off-by: Akihiko Odaki <akihiko.odaki@gmail.com> Message-Id: <20210223060307.87736-1-akihiko.odaki@gmail.com> Signed-off-by: Gerd Hoffmann <kraxel@redhat.com>
487 lines
14 KiB
C
487 lines
14 KiB
C
/*
|
|
* Copyright (C) 2015-2016 Gerd Hoffmann <kraxel@redhat.com>
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2.1 of the License, or (at your option) any later version.
|
|
*
|
|
* This library 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
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with this library; if not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
#include "qemu/osdep.h"
|
|
#include "qemu/drm.h"
|
|
#include "qemu/error-report.h"
|
|
#include "ui/console.h"
|
|
#include "ui/egl-helpers.h"
|
|
|
|
EGLDisplay *qemu_egl_display;
|
|
EGLConfig qemu_egl_config;
|
|
DisplayGLMode qemu_egl_mode;
|
|
|
|
/* ------------------------------------------------------------------ */
|
|
|
|
static void egl_fb_delete_texture(egl_fb *fb)
|
|
{
|
|
if (!fb->delete_texture) {
|
|
return;
|
|
}
|
|
|
|
glDeleteTextures(1, &fb->texture);
|
|
fb->delete_texture = false;
|
|
}
|
|
|
|
void egl_fb_destroy(egl_fb *fb)
|
|
{
|
|
if (!fb->framebuffer) {
|
|
return;
|
|
}
|
|
|
|
egl_fb_delete_texture(fb);
|
|
glDeleteFramebuffers(1, &fb->framebuffer);
|
|
|
|
fb->width = 0;
|
|
fb->height = 0;
|
|
fb->texture = 0;
|
|
fb->framebuffer = 0;
|
|
}
|
|
|
|
void egl_fb_setup_default(egl_fb *fb, int width, int height)
|
|
{
|
|
fb->width = width;
|
|
fb->height = height;
|
|
fb->framebuffer = 0; /* default framebuffer */
|
|
}
|
|
|
|
void egl_fb_setup_for_tex(egl_fb *fb, int width, int height,
|
|
GLuint texture, bool delete)
|
|
{
|
|
egl_fb_delete_texture(fb);
|
|
|
|
fb->width = width;
|
|
fb->height = height;
|
|
fb->texture = texture;
|
|
fb->delete_texture = delete;
|
|
if (!fb->framebuffer) {
|
|
glGenFramebuffers(1, &fb->framebuffer);
|
|
}
|
|
|
|
glBindFramebuffer(GL_FRAMEBUFFER_EXT, fb->framebuffer);
|
|
glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT,
|
|
GL_TEXTURE_2D, fb->texture, 0);
|
|
}
|
|
|
|
void egl_fb_setup_new_tex(egl_fb *fb, int width, int height)
|
|
{
|
|
GLuint texture;
|
|
|
|
glGenTextures(1, &texture);
|
|
glBindTexture(GL_TEXTURE_2D, texture);
|
|
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height,
|
|
0, GL_BGRA, GL_UNSIGNED_BYTE, 0);
|
|
|
|
egl_fb_setup_for_tex(fb, width, height, texture, true);
|
|
}
|
|
|
|
void egl_fb_blit(egl_fb *dst, egl_fb *src, bool flip)
|
|
{
|
|
GLuint y1, y2;
|
|
|
|
glBindFramebuffer(GL_READ_FRAMEBUFFER, src->framebuffer);
|
|
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, dst->framebuffer);
|
|
glViewport(0, 0, dst->width, dst->height);
|
|
y1 = flip ? src->height : 0;
|
|
y2 = flip ? 0 : src->height;
|
|
glBlitFramebuffer(0, y1, src->width, y2,
|
|
0, 0, dst->width, dst->height,
|
|
GL_COLOR_BUFFER_BIT, GL_LINEAR);
|
|
}
|
|
|
|
void egl_fb_read(DisplaySurface *dst, egl_fb *src)
|
|
{
|
|
glBindFramebuffer(GL_READ_FRAMEBUFFER, src->framebuffer);
|
|
glReadBuffer(GL_COLOR_ATTACHMENT0_EXT);
|
|
glReadPixels(0, 0, surface_width(dst), surface_height(dst),
|
|
GL_BGRA, GL_UNSIGNED_BYTE, surface_data(dst));
|
|
}
|
|
|
|
void egl_texture_blit(QemuGLShader *gls, egl_fb *dst, egl_fb *src, bool flip)
|
|
{
|
|
glBindFramebuffer(GL_FRAMEBUFFER_EXT, dst->framebuffer);
|
|
glViewport(0, 0, dst->width, dst->height);
|
|
glEnable(GL_TEXTURE_2D);
|
|
glBindTexture(GL_TEXTURE_2D, src->texture);
|
|
qemu_gl_run_texture_blit(gls, flip);
|
|
}
|
|
|
|
void egl_texture_blend(QemuGLShader *gls, egl_fb *dst, egl_fb *src, bool flip,
|
|
int x, int y, double scale_x, double scale_y)
|
|
{
|
|
glBindFramebuffer(GL_FRAMEBUFFER_EXT, dst->framebuffer);
|
|
int w = scale_x * src->width;
|
|
int h = scale_y * src->height;
|
|
if (flip) {
|
|
glViewport(x, y, w, h);
|
|
} else {
|
|
glViewport(x, dst->height - h - y, w, h);
|
|
}
|
|
glEnable(GL_TEXTURE_2D);
|
|
glBindTexture(GL_TEXTURE_2D, src->texture);
|
|
glEnable(GL_BLEND);
|
|
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
|
qemu_gl_run_texture_blit(gls, flip);
|
|
glDisable(GL_BLEND);
|
|
}
|
|
|
|
/* ---------------------------------------------------------------------- */
|
|
|
|
#ifdef CONFIG_GBM
|
|
|
|
int qemu_egl_rn_fd;
|
|
struct gbm_device *qemu_egl_rn_gbm_dev;
|
|
EGLContext qemu_egl_rn_ctx;
|
|
|
|
int egl_rendernode_init(const char *rendernode, DisplayGLMode mode)
|
|
{
|
|
qemu_egl_rn_fd = -1;
|
|
int rc;
|
|
|
|
qemu_egl_rn_fd = qemu_drm_rendernode_open(rendernode);
|
|
if (qemu_egl_rn_fd == -1) {
|
|
error_report("egl: no drm render node available");
|
|
goto err;
|
|
}
|
|
|
|
qemu_egl_rn_gbm_dev = gbm_create_device(qemu_egl_rn_fd);
|
|
if (!qemu_egl_rn_gbm_dev) {
|
|
error_report("egl: gbm_create_device failed");
|
|
goto err;
|
|
}
|
|
|
|
rc = qemu_egl_init_dpy_mesa((EGLNativeDisplayType)qemu_egl_rn_gbm_dev,
|
|
mode);
|
|
if (rc != 0) {
|
|
/* qemu_egl_init_dpy_mesa reports error */
|
|
goto err;
|
|
}
|
|
|
|
if (!epoxy_has_egl_extension(qemu_egl_display,
|
|
"EGL_KHR_surfaceless_context")) {
|
|
error_report("egl: EGL_KHR_surfaceless_context not supported");
|
|
goto err;
|
|
}
|
|
if (!epoxy_has_egl_extension(qemu_egl_display,
|
|
"EGL_MESA_image_dma_buf_export")) {
|
|
error_report("egl: EGL_MESA_image_dma_buf_export not supported");
|
|
goto err;
|
|
}
|
|
|
|
qemu_egl_rn_ctx = qemu_egl_init_ctx();
|
|
if (!qemu_egl_rn_ctx) {
|
|
error_report("egl: egl_init_ctx failed");
|
|
goto err;
|
|
}
|
|
|
|
return 0;
|
|
|
|
err:
|
|
if (qemu_egl_rn_gbm_dev) {
|
|
gbm_device_destroy(qemu_egl_rn_gbm_dev);
|
|
}
|
|
if (qemu_egl_rn_fd != -1) {
|
|
close(qemu_egl_rn_fd);
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
int egl_get_fd_for_texture(uint32_t tex_id, EGLint *stride, EGLint *fourcc,
|
|
EGLuint64KHR *modifier)
|
|
{
|
|
EGLImageKHR image;
|
|
EGLint num_planes, fd;
|
|
|
|
image = eglCreateImageKHR(qemu_egl_display, eglGetCurrentContext(),
|
|
EGL_GL_TEXTURE_2D_KHR,
|
|
(EGLClientBuffer)(unsigned long)tex_id,
|
|
NULL);
|
|
if (!image) {
|
|
return -1;
|
|
}
|
|
|
|
eglExportDMABUFImageQueryMESA(qemu_egl_display, image, fourcc,
|
|
&num_planes, modifier);
|
|
if (num_planes != 1) {
|
|
eglDestroyImageKHR(qemu_egl_display, image);
|
|
return -1;
|
|
}
|
|
eglExportDMABUFImageMESA(qemu_egl_display, image, &fd, stride, NULL);
|
|
eglDestroyImageKHR(qemu_egl_display, image);
|
|
|
|
return fd;
|
|
}
|
|
|
|
void egl_dmabuf_import_texture(QemuDmaBuf *dmabuf)
|
|
{
|
|
EGLImageKHR image = EGL_NO_IMAGE_KHR;
|
|
EGLint attrs[64];
|
|
int i = 0;
|
|
|
|
if (dmabuf->texture != 0) {
|
|
return;
|
|
}
|
|
|
|
attrs[i++] = EGL_WIDTH;
|
|
attrs[i++] = dmabuf->width;
|
|
attrs[i++] = EGL_HEIGHT;
|
|
attrs[i++] = dmabuf->height;
|
|
attrs[i++] = EGL_LINUX_DRM_FOURCC_EXT;
|
|
attrs[i++] = dmabuf->fourcc;
|
|
|
|
attrs[i++] = EGL_DMA_BUF_PLANE0_FD_EXT;
|
|
attrs[i++] = dmabuf->fd;
|
|
attrs[i++] = EGL_DMA_BUF_PLANE0_PITCH_EXT;
|
|
attrs[i++] = dmabuf->stride;
|
|
attrs[i++] = EGL_DMA_BUF_PLANE0_OFFSET_EXT;
|
|
attrs[i++] = 0;
|
|
#ifdef EGL_DMA_BUF_PLANE0_MODIFIER_LO_EXT
|
|
if (dmabuf->modifier) {
|
|
attrs[i++] = EGL_DMA_BUF_PLANE0_MODIFIER_LO_EXT;
|
|
attrs[i++] = (dmabuf->modifier >> 0) & 0xffffffff;
|
|
attrs[i++] = EGL_DMA_BUF_PLANE0_MODIFIER_HI_EXT;
|
|
attrs[i++] = (dmabuf->modifier >> 32) & 0xffffffff;
|
|
}
|
|
#endif
|
|
attrs[i++] = EGL_NONE;
|
|
|
|
image = eglCreateImageKHR(qemu_egl_display,
|
|
EGL_NO_CONTEXT,
|
|
EGL_LINUX_DMA_BUF_EXT,
|
|
NULL, attrs);
|
|
if (image == EGL_NO_IMAGE_KHR) {
|
|
error_report("eglCreateImageKHR failed");
|
|
return;
|
|
}
|
|
|
|
glGenTextures(1, &dmabuf->texture);
|
|
glBindTexture(GL_TEXTURE_2D, dmabuf->texture);
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
|
|
|
glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, (GLeglImageOES)image);
|
|
eglDestroyImageKHR(qemu_egl_display, image);
|
|
}
|
|
|
|
void egl_dmabuf_release_texture(QemuDmaBuf *dmabuf)
|
|
{
|
|
if (dmabuf->texture == 0) {
|
|
return;
|
|
}
|
|
|
|
glDeleteTextures(1, &dmabuf->texture);
|
|
dmabuf->texture = 0;
|
|
}
|
|
|
|
#endif /* CONFIG_GBM */
|
|
|
|
/* ---------------------------------------------------------------------- */
|
|
|
|
EGLSurface qemu_egl_init_surface_x11(EGLContext ectx, EGLNativeWindowType win)
|
|
{
|
|
EGLSurface esurface;
|
|
EGLBoolean b;
|
|
|
|
esurface = eglCreateWindowSurface(qemu_egl_display,
|
|
qemu_egl_config,
|
|
win, NULL);
|
|
if (esurface == EGL_NO_SURFACE) {
|
|
error_report("egl: eglCreateWindowSurface failed");
|
|
return NULL;
|
|
}
|
|
|
|
b = eglMakeCurrent(qemu_egl_display, esurface, esurface, ectx);
|
|
if (b == EGL_FALSE) {
|
|
error_report("egl: eglMakeCurrent failed");
|
|
return NULL;
|
|
}
|
|
|
|
return esurface;
|
|
}
|
|
|
|
/* ---------------------------------------------------------------------- */
|
|
|
|
#if defined(CONFIG_X11) || defined(CONFIG_GBM)
|
|
|
|
/*
|
|
* Taken from glamor_egl.h from the Xorg xserver, which is MIT licensed
|
|
*
|
|
* Create an EGLDisplay from a native display type. This is a little quirky
|
|
* for a few reasons.
|
|
*
|
|
* 1: GetPlatformDisplayEXT and GetPlatformDisplay are the API you want to
|
|
* use, but have different function signatures in the third argument; this
|
|
* happens not to matter for us, at the moment, but it means epoxy won't alias
|
|
* them together.
|
|
*
|
|
* 2: epoxy 1.3 and earlier don't understand EGL client extensions, which
|
|
* means you can't call "eglGetPlatformDisplayEXT" directly, as the resolver
|
|
* will crash.
|
|
*
|
|
* 3: You can't tell whether you have EGL 1.5 at this point, because
|
|
* eglQueryString(EGL_VERSION) is a property of the display, which we don't
|
|
* have yet. So you have to query for extensions no matter what. Fortunately
|
|
* epoxy_has_egl_extension _does_ let you query for client extensions, so
|
|
* we don't have to write our own extension string parsing.
|
|
*
|
|
* 4. There is no EGL_KHR_platform_base to complement the EXT one, thus one
|
|
* needs to know EGL 1.5 is supported in order to use the eglGetPlatformDisplay
|
|
* function pointer.
|
|
* We can workaround this (circular dependency) by probing for the EGL 1.5
|
|
* platform extensions (EGL_KHR_platform_gbm and friends) yet it doesn't seem
|
|
* like mesa will be able to advertise these (even though it can do EGL 1.5).
|
|
*/
|
|
static EGLDisplay qemu_egl_get_display(EGLNativeDisplayType native,
|
|
EGLenum platform)
|
|
{
|
|
EGLDisplay dpy = EGL_NO_DISPLAY;
|
|
|
|
/* In practise any EGL 1.5 implementation would support the EXT extension */
|
|
if (epoxy_has_egl_extension(NULL, "EGL_EXT_platform_base")) {
|
|
PFNEGLGETPLATFORMDISPLAYEXTPROC getPlatformDisplayEXT =
|
|
(void *) eglGetProcAddress("eglGetPlatformDisplayEXT");
|
|
if (getPlatformDisplayEXT && platform != 0) {
|
|
dpy = getPlatformDisplayEXT(platform, native, NULL);
|
|
}
|
|
}
|
|
|
|
if (dpy == EGL_NO_DISPLAY) {
|
|
/* fallback */
|
|
dpy = eglGetDisplay(native);
|
|
}
|
|
return dpy;
|
|
}
|
|
|
|
static int qemu_egl_init_dpy(EGLNativeDisplayType dpy,
|
|
EGLenum platform,
|
|
DisplayGLMode mode)
|
|
{
|
|
static const EGLint conf_att_core[] = {
|
|
EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
|
|
EGL_RENDERABLE_TYPE, EGL_OPENGL_BIT,
|
|
EGL_RED_SIZE, 5,
|
|
EGL_GREEN_SIZE, 5,
|
|
EGL_BLUE_SIZE, 5,
|
|
EGL_ALPHA_SIZE, 0,
|
|
EGL_NONE,
|
|
};
|
|
static const EGLint conf_att_gles[] = {
|
|
EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
|
|
EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
|
|
EGL_RED_SIZE, 5,
|
|
EGL_GREEN_SIZE, 5,
|
|
EGL_BLUE_SIZE, 5,
|
|
EGL_ALPHA_SIZE, 0,
|
|
EGL_NONE,
|
|
};
|
|
EGLint major, minor;
|
|
EGLBoolean b;
|
|
EGLint n;
|
|
bool gles = (mode == DISPLAYGL_MODE_ES);
|
|
|
|
qemu_egl_display = qemu_egl_get_display(dpy, platform);
|
|
if (qemu_egl_display == EGL_NO_DISPLAY) {
|
|
error_report("egl: eglGetDisplay failed");
|
|
return -1;
|
|
}
|
|
|
|
b = eglInitialize(qemu_egl_display, &major, &minor);
|
|
if (b == EGL_FALSE) {
|
|
error_report("egl: eglInitialize failed");
|
|
return -1;
|
|
}
|
|
|
|
b = eglBindAPI(gles ? EGL_OPENGL_ES_API : EGL_OPENGL_API);
|
|
if (b == EGL_FALSE) {
|
|
error_report("egl: eglBindAPI failed (%s mode)",
|
|
gles ? "gles" : "core");
|
|
return -1;
|
|
}
|
|
|
|
b = eglChooseConfig(qemu_egl_display,
|
|
gles ? conf_att_gles : conf_att_core,
|
|
&qemu_egl_config, 1, &n);
|
|
if (b == EGL_FALSE || n != 1) {
|
|
error_report("egl: eglChooseConfig failed (%s mode)",
|
|
gles ? "gles" : "core");
|
|
return -1;
|
|
}
|
|
|
|
qemu_egl_mode = gles ? DISPLAYGL_MODE_ES : DISPLAYGL_MODE_CORE;
|
|
return 0;
|
|
}
|
|
|
|
int qemu_egl_init_dpy_x11(EGLNativeDisplayType dpy, DisplayGLMode mode)
|
|
{
|
|
#ifdef EGL_KHR_platform_x11
|
|
return qemu_egl_init_dpy(dpy, EGL_PLATFORM_X11_KHR, mode);
|
|
#else
|
|
return qemu_egl_init_dpy(dpy, 0, mode);
|
|
#endif
|
|
}
|
|
|
|
int qemu_egl_init_dpy_mesa(EGLNativeDisplayType dpy, DisplayGLMode mode)
|
|
{
|
|
#ifdef EGL_MESA_platform_gbm
|
|
return qemu_egl_init_dpy(dpy, EGL_PLATFORM_GBM_MESA, mode);
|
|
#else
|
|
return qemu_egl_init_dpy(dpy, 0, mode);
|
|
#endif
|
|
}
|
|
|
|
#endif
|
|
|
|
bool qemu_egl_has_dmabuf(void)
|
|
{
|
|
if (qemu_egl_display == EGL_NO_DISPLAY) {
|
|
return false;
|
|
}
|
|
|
|
return epoxy_has_egl_extension(qemu_egl_display,
|
|
"EGL_EXT_image_dma_buf_import");
|
|
}
|
|
|
|
EGLContext qemu_egl_init_ctx(void)
|
|
{
|
|
static const EGLint ctx_att_core[] = {
|
|
EGL_CONTEXT_OPENGL_PROFILE_MASK, EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT,
|
|
EGL_NONE
|
|
};
|
|
static const EGLint ctx_att_gles[] = {
|
|
EGL_CONTEXT_CLIENT_VERSION, 2,
|
|
EGL_NONE
|
|
};
|
|
bool gles = (qemu_egl_mode == DISPLAYGL_MODE_ES);
|
|
EGLContext ectx;
|
|
EGLBoolean b;
|
|
|
|
ectx = eglCreateContext(qemu_egl_display, qemu_egl_config, EGL_NO_CONTEXT,
|
|
gles ? ctx_att_gles : ctx_att_core);
|
|
if (ectx == EGL_NO_CONTEXT) {
|
|
error_report("egl: eglCreateContext failed");
|
|
return NULL;
|
|
}
|
|
|
|
b = eglMakeCurrent(qemu_egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE, ectx);
|
|
if (b == EGL_FALSE) {
|
|
error_report("egl: eglMakeCurrent failed");
|
|
return NULL;
|
|
}
|
|
|
|
return ectx;
|
|
}
|