12to11/egl.c
dec05eba e0c73347ca egl: fix EGL crash on nvidia (createPlatformWindowSurface fail)
createPlatformWindowSurface fails and returns NO_SURFACE which
aborts the program.
Nvidia doesn't seem to support EGL_BUFFER_SIZE 32 with opengl es,
and it seems like we dont need it anyways?
2025-06-16 22:31:14 +02:00

2589 lines
67 KiB
C

/* Wayland compositor running on top of an X server.
Copyright (C) 2022 to various contributors.
This file is part of 12to11.
12to11 is free software: you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by the
Free Software Foundation, either version 3 of the License, or (at your
option) any later version.
12to11 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 12to11. If not, see <https://www.gnu.org/licenses/>. */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stddef.h>
#include <drm_fourcc.h>
#include "compositor.h"
#include "shaders.h"
#include <EGL/egl.h>
#include <EGL/eglext.h>
#include <GLES2/gl2.h>
#include <GLES2/gl2ext.h>
#include "linux-dmabuf-unstable-v1.h"
/* These are flags for the DrmFormats. */
enum
{
NeedExternalTarget = 1,
};
typedef enum _EglBufferType EglBufferType;
typedef struct _EglTarget EglTarget;
typedef struct _EglBuffer EglBuffer;
typedef struct _EglDmaBufBuffer EglDmaBufBuffer;
typedef struct _EglShmBuffer EglShmBuffer;
typedef struct _EglSinglePixelBuffer EglSinglePixelBuffer;
typedef struct _FormatInfo FormatInfo;
typedef struct _CompositeProgram CompositeProgram;
enum _EglBufferType
{
DmaBufBuffer,
ShmBuffer,
SinglePixelBuffer,
};
struct _EglDmaBufBuffer
{
/* The type of this buffer. Always DmaBufBuffer. */
EglBufferType type;
/* The EGL image associated with this buffer. */
EGLImageKHR *image;
/* DRM format used to create this buffer. */
DrmFormat *format;
};
struct _EglShmBuffer
{
/* The type of this buffer. Always ShmBuffer. */
EglBufferType type;
/* The pointer to pool data. */
void **data;
/* The offset and stride of the buffer. */
int32_t offset, stride;
/* The format info of this buffer. */
FormatInfo *format;
};
struct _EglSinglePixelBuffer
{
/* The type of this buffer. Always DmaBufBuffer. */
EglBufferType type;
/* The red, green, blue and alpha values. */
float r, g, b, a;
};
struct _FormatInfo
{
/* The corresponding Wayland format. */
uint32_t wl_format;
/* The corresponding DRM format. */
uint32_t drm_format;
/* The GL internalFormat. If 0, then it is actually gl_format. */
GLint gl_internalformat;
/* The GL format and type. */
GLint gl_format, gl_type;
/* Bits per pixel. */
short bpp;
/* Whether or not an alpha channel is present. */
Bool has_alpha : 1;
};
enum
{
IsTextureGenerated = 1,
HasAlpha = (1 << 2),
CanRelease = (1 << 3),
InvertY = (1 << 4),
};
struct _EglBuffer
{
/* Some flags. */
int flags;
/* The texture name of any generated texture. */
GLuint texture;
/* 3x3 matrix that is used to transform texcoord into actual texture
coordinates. Note that GLfloat[9] should be the same type as
Matrix. */
GLfloat matrix[9];
/* The width and height of the buffer. */
int width, height;
/* Various different buffers. */
union {
/* The type of the buffer. */
EglBufferType type;
/* A dma-buf buffer. */
EglDmaBufBuffer dmabuf;
/* A shared memory buffer. */
EglShmBuffer shm;
/* A single-pixel buffer. */
EglSinglePixelBuffer single_pixel;
} u;
};
/* This macro computes the size of an EglBuffer for the given type.
It is used to only allocate as much memory as really required. */
#define EglBufferSize(type) \
(offsetof (EglBuffer, u) + sizeof (type))
enum
{
SwapPreservesContents = 1,
IsPixmap = 2,
};
struct _EglTarget
{
/* The drawable backing this surface. */
Drawable source;
/* The EGL surface. */
EGLSurface surface;
/* The width and height of the backing drawable. */
unsigned short width, height;
/* Various flags. */
int flags;
};
struct _CompositeProgram
{
/* The name of the program. */
GLint program;
/* The index of the texcoord attribute. */
GLuint texcoord;
/* The index of the position attribute. */
GLuint position;
/* The index of the texture uniform. */
GLuint texture;
/* The index of the source uniform. */
GLuint source;
/* The index of the invert_y uniform. */
GLuint invert_y;
/* The index of the source_pixel uniform. */
GLuint source_color;
};
/* This macro makes column major order easier to reason about for C
folks. */
#define Index(matrix, row, column) ((matrix)[(column) * 3 + (row)])
/* All known SHM formats. */
static FormatInfo known_shm_formats[] =
{
{
.wl_format = WL_SHM_FORMAT_ARGB8888,
.drm_format = WL_SHM_FORMAT_ARGB8888,
.gl_format = GL_BGRA_EXT,
.gl_type = GL_UNSIGNED_BYTE,
.has_alpha = True,
.bpp = 32,
},
{
.wl_format = WL_SHM_FORMAT_XRGB8888,
.drm_format = WL_SHM_FORMAT_XRGB8888,
.gl_format = GL_BGRA_EXT,
.gl_type = GL_UNSIGNED_BYTE,
.has_alpha = False,
.bpp = 32,
},
{
.wl_format = WL_SHM_FORMAT_XBGR8888,
.drm_format = DRM_FORMAT_XBGR8888,
.gl_format = GL_RGBA,
.gl_type = GL_UNSIGNED_BYTE,
.has_alpha = False,
.bpp = 32,
},
{
.wl_format = WL_SHM_FORMAT_ABGR8888,
.drm_format = DRM_FORMAT_ABGR8888,
.gl_format = GL_RGBA,
.gl_type = GL_UNSIGNED_BYTE,
.has_alpha = True,
.bpp = 32,
},
{
.wl_format = WL_SHM_FORMAT_BGR888,
.drm_format = DRM_FORMAT_BGR888,
.gl_format = GL_RGB,
.gl_type = GL_UNSIGNED_BYTE,
.has_alpha = False,
.bpp = 24,
},
{
.wl_format = WL_SHM_FORMAT_RGBX4444,
.drm_format = DRM_FORMAT_RGBX4444,
.gl_format = GL_RGBA,
.gl_type = GL_UNSIGNED_SHORT_4_4_4_4,
.has_alpha = False,
.bpp = 16,
},
{
.wl_format = WL_SHM_FORMAT_RGBA4444,
.drm_format = DRM_FORMAT_RGBA4444,
.gl_format = GL_RGBA,
.gl_type = GL_UNSIGNED_SHORT_4_4_4_4,
.has_alpha = True,
.bpp = 16,
},
{
.wl_format = WL_SHM_FORMAT_RGBX5551,
.drm_format = DRM_FORMAT_RGBX5551,
.gl_format = GL_RGBA,
.gl_type = GL_UNSIGNED_SHORT_5_5_5_1,
.has_alpha = False,
.bpp = 16,
},
{
.wl_format = WL_SHM_FORMAT_RGBA5551,
.drm_format = DRM_FORMAT_RGBA5551,
.gl_format = GL_RGBA,
.gl_type = GL_UNSIGNED_SHORT_5_5_5_1,
.has_alpha = True,
.bpp = 16,
},
{
.wl_format = WL_SHM_FORMAT_RGB565,
.drm_format = DRM_FORMAT_RGB565,
.gl_format = GL_RGB,
.gl_type = GL_UNSIGNED_SHORT_5_6_5,
.has_alpha = False,
.bpp = 16,
},
{
.wl_format = WL_SHM_FORMAT_XBGR2101010,
.drm_format = DRM_FORMAT_XBGR2101010,
.gl_format = GL_RGBA,
.gl_type = GL_UNSIGNED_INT_2_10_10_10_REV_EXT,
.has_alpha = False,
.bpp = 32,
},
{
.wl_format = WL_SHM_FORMAT_ABGR2101010,
.drm_format = DRM_FORMAT_ABGR2101010,
.gl_format = GL_RGBA,
.gl_type = GL_UNSIGNED_INT_2_10_10_10_REV_EXT,
.has_alpha = True,
.bpp = 32,
},
{
.wl_format = WL_SHM_FORMAT_XBGR16161616,
.drm_format = DRM_FORMAT_XBGR16161616,
.gl_internalformat = GL_RGBA16_EXT,
.gl_format = GL_RGBA,
.gl_type = GL_UNSIGNED_SHORT,
.has_alpha = False,
.bpp = 64,
},
{
.wl_format = WL_SHM_FORMAT_ABGR16161616,
.drm_format = DRM_FORMAT_ABGR16161616,
.gl_internalformat = GL_RGBA16_EXT,
.gl_format = GL_RGBA,
.gl_type = GL_UNSIGNED_SHORT,
.has_alpha = True,
.bpp = 64,
},
};
/* GL procedures needed. */
static PFNEGLGETPLATFORMDISPLAYPROC IGetPlatformDisplay;
static PFNEGLCREATEPLATFORMWINDOWSURFACEPROC ICreatePlatformWindowSurface;
static PFNEGLCREATEPLATFORMPIXMAPSURFACEPROC ICreatePlatformPixmapSurface;
static PFNEGLCREATEIMAGEKHRPROC ICreateImage;
static PFNEGLDESTROYIMAGEKHRPROC IDestroyImage;
static PFNGLEGLIMAGETARGETTEXTURE2DOESPROC IEGLImageTargetTexture2D;
/* GL procedures that are optional. */
static PFNEGLQUERYDISPLAYATTRIBEXTPROC IQueryDisplayAttrib;
static PFNEGLQUERYDEVICESTRINGEXTPROC IQueryDeviceString;
static PFNEGLQUERYDMABUFFORMATSEXTPROC IQueryDmaBufFormats;
static PFNEGLQUERYDMABUFMODIFIERSEXTPROC IQueryDmaBufModifiers;
static PFNEGLCREATESYNCKHRPROC ICreateSync;
static PFNEGLDESTROYSYNCKHRPROC IDestroySync;
static PFNEGLCLIENTWAITSYNCKHRPROC IClientWaitSync;
static PFNEGLGETSYNCATTRIBKHRPROC IGetSyncAttrib;
static PFNEGLWAITSYNCKHRPROC IWaitSync;
static PFNEGLDUPNATIVEFENCEFDANDROIDPROC IDupNativeFenceFD;
static PFNEGLSWAPBUFFERSWITHDAMAGEEXTPROC ISwapBuffersWithDamage;
/* The EGL display handle. */
static EGLDisplay egl_display;
/* The EGL context handle. */
static EGLContext egl_context;
/* The chosen framebuffer configuration. */
static EGLConfig egl_config;
/* The current render target. */
static EglTarget *current_target;
/* The major and minor versions of EGL. */
static EGLint egl_major, egl_minor;
/* The DRM device node. */
static dev_t drm_device;
/* Whether or not the device node is available. */
static Bool drm_device_available;
/* List of DRM formats provided. */
static DrmFormat *drm_formats;
/* Number of DRM formats provided. */
static int n_drm_formats;
/* List of SHM formats provided. */
static ShmFormat *shm_formats;
/* Number of SHM formats provided. */
static int n_shm_formats;
/* Global shader programs. */
static GLint clear_rect_program;
/* Index of position attrib. */
static GLuint clear_rect_program_pos_attrib;
/* The picture format used for cursors. */
static XRenderPictFormat *cursor_format;
/* Composition program for ARGB textures. */
static CompositeProgram argb_program;
/* Composition program for XRGB textures. */
static CompositeProgram xrgb_program;
/* Composition program for external textures. */
static CompositeProgram external_program;
/* Composition program for single pixel buffers. */
static CompositeProgram single_pixel_buffer_program;
/* Whether or not buffer age is supported. */
static Bool have_egl_ext_buffer_age;
/* EGL and GLES 2-based renderer. */
#define CheckExtension(name) \
if (!name) \
{ \
if (display) \
eglTerminate (display); \
fprintf (stderr, "Missing: egl%s\n", #name + 1); \
return False; \
}
#define CheckExtensionGl(name) \
if (!name) \
{ \
/* If the context remains current, then nothing \
will get released upon eglTerminate. */ \
eglMakeCurrent (display, EGL_NO_SURFACE, \
EGL_NO_SURFACE, EGL_NO_CONTEXT); \
eglTerminate (display); \
fprintf (stderr, "Missing: gl%s\n", #name + 1); \
return False; \
}
#define LoadProc(name, ext, extname) \
if (HaveEglExtension (extname)) \
I##name \
= (void *) eglGetProcAddress ("egl" #name ext)
#define LoadProcGl(name, ext, extname) \
if (HaveGlExtension (extname)) \
I##name \
= (void *) eglGetProcAddress ("gl" #name ext)
#define CheckGlExtension(name) \
if (!HaveGlExtension (name)) \
{ \
fprintf (stderr, "Missing %s\n", name); \
\
eglMakeCurrent (display, EGL_NO_SURFACE, \
EGL_NO_SURFACE, \
EGL_NO_CONTEXT); \
eglTerminate (display); \
return False; \
}
static Bool
HaveEglExtension1 (const char *extensions, const char *extension)
{
const char *end;
size_t extlen, n;
extlen = strlen (extension);
end = extensions + strlen (extensions);
while (extensions < end)
{
/* Skip spaces, if any. */
if (*extensions == ' ')
{
extensions++;
continue;
}
n = strcspn (extensions, " ");
/* Compare strings. */
if (n == extlen && !strncmp (extension, extensions, n))
return True;
extensions += n;
}
/* Not found. */
return False;
}
static Bool
HaveEglExtension (const char *extension)
{
const char *extensions;
if (egl_display)
extensions = eglQueryString (egl_display, EGL_EXTENSIONS);
else
extensions = eglQueryString (EGL_NO_DISPLAY, EGL_EXTENSIONS);
if (!extensions)
return False;
return HaveEglExtension1 (extensions, extension);
}
static Bool
HaveGlExtension (const char *extension)
{
const GLubyte *extensions;
extensions = glGetString (GL_EXTENSIONS);
if (!extensions)
return False;
return HaveEglExtension1 ((const char *) extensions, extension);
}
static void
EglInitFuncsEarly (void)
{
LoadProc (GetPlatformDisplay, "", "EGL_EXT_platform_base");
LoadProc (CreatePlatformWindowSurface, "", "EGL_EXT_platform_base");
LoadProc (CreatePlatformPixmapSurface, "", "EGL_EXT_platform_base");
/* These extensions are not really required. */
LoadProc (QueryDisplayAttrib, "EXT", "EGL_EXT_device_query");
LoadProc (QueryDeviceString, "EXT", "EGL_EXT_device_query");
}
static void
EglInitFuncs (void)
{
/* Initialize extensions. */
LoadProc (CreateImage, "KHR", "EGL_KHR_image_base");
LoadProc (DestroyImage, "KHR", "EGL_KHR_image_base");
/* Initialize extensions that are not really required. */
LoadProc (QueryDmaBufFormats, "EXT",
"EGL_EXT_image_dma_buf_import_modifiers");
LoadProc (QueryDmaBufModifiers, "EXT",
"EGL_EXT_image_dma_buf_import_modifiers");
LoadProc (CreateSync, "KHR", "EGL_KHR_fence_sync");
LoadProc (DestroySync, "KHR", "EGL_KHR_fence_sync");
LoadProc (ClientWaitSync, "KHR", "EGL_KHR_fence_sync");
LoadProc (GetSyncAttrib, "KHR", "EGL_KHR_fence_sync");
LoadProc (WaitSync, "KHR", "EGL_KHR_wait_sync");
LoadProc (DupNativeFenceFD, "ANDROID", "EGL_ANDROID_native_fence_sync");
LoadProc (SwapBuffersWithDamage, "EXT",
"EGL_EXT_swap_buffers_with_damage");
}
static void
EglInitGlFuncs (void)
{
LoadProcGl (EGLImageTargetTexture2D, "OES", "GL_OES_EGL_image");
/* We treat eglWaitSyncKHR specially, since it only works if the
server client API also supports GL_OES_EGL_sync. */
if (!HaveGlExtension ("GL_OES_EGL_sync"))
IWaitSync = NULL;
}
static Visual *
PickBetterVisual (Visual *visual, int *depth)
{
XRenderPictFormat target_format, *format, *found;
int i, num_x_formats, bpp, n_visuals, j;
EGLint alpha_size;
XPixmapFormatValues *formats;
XVisualInfo empty_template, *visuals;
/* First, see if there is already an alpha channel. */
format = XRenderFindVisualFormat (compositor.display, visual);
if (!format)
return visual;
if (format->type != PictTypeDirect)
/* Can this actually happen? */
return visual;
if (format->direct.alphaMask)
return visual;
/* Next, build the target format from the visual format. */
target_format.type = PictTypeDirect;
target_format.direct = format->direct;
/* Obtain the size of the alpha mask in the EGL config. */
if (!eglGetConfigAttrib (egl_display, egl_config,
EGL_ALPHA_SIZE, &alpha_size))
return visual;
if (alpha_size > 16)
/* If the alpha mask is too big, then use the chosen visual. */
return visual;
/* Add the alpha mask. */
for (i = 0; i < alpha_size; ++i)
target_format.direct.alphaMask |= 1 << i;
/* Look for matching picture formats with the same bpp and a larger
depth. */
formats = XListPixmapFormats (compositor.display, &num_x_formats);
if (!formats)
return visual;
/* Obtain the number of bits per pixel for the given depth. */
bpp = 0;
for (i = 0; i < num_x_formats; ++i)
{
if (formats[i].depth == format->depth)
bpp = formats[i].bits_per_pixel;
}
if (!bpp)
{
XFree (formats);
return visual;
}
/* Get a list of all visuals. */
empty_template.screen = DefaultScreen (compositor.display);
visuals = XGetVisualInfo (compositor.display, VisualScreenMask,
&empty_template, &n_visuals);
if (!visuals)
{
XFree (formats);
return visual;
}
/* Now, loop through each depth. */
for (i = 0; i < num_x_formats; ++i)
{
if (formats[i].depth > format->depth
&& formats[i].bits_per_pixel == bpp)
{
/* Try to find a matching picture format. */
target_format.depth = formats[i].depth;
found = XRenderFindFormat (compositor.display,
PictFormatType
| PictFormatDepth
| PictFormatRed
| PictFormatGreen
| PictFormatBlue
| PictFormatRedMask
| PictFormatBlueMask
| PictFormatGreenMask
| PictFormatAlphaMask,
&target_format, 0);
if (found)
{
/* Now try to find the corresponding visual. */
for (j = 0; j < n_visuals; ++j)
{
if (visuals[j].depth != formats[i].depth)
continue;
if (XRenderFindVisualFormat (compositor.display,
visuals[j].visual) == found)
{
/* We got a usable visual with an alpha channel
otherwise matching the characteristics of the
visual specified by EGL. Return. */
*depth = formats[i].depth;
visual = visuals[j].visual;
XFree (visuals);
XFree (formats);
return visual;
}
}
}
}
}
/* Otherwise, nothing was found. Return the original visual
untouched, but free visuals. */
XFree (visuals);
XFree (formats);
return visual;
}
static Visual *
FindVisual (VisualID visual, int *depth)
{
XVisualInfo vinfo, *visuals;
Visual *value;
int nvisuals;
const char *override;
/* Normally, we do not want to manually specify this. However, EGL
happens to be buggy, and cannot find visuals with an alpha
mask. */
override = getenv ("RENDER_VISUAL");
if (!override)
vinfo.visualid = visual;
else
vinfo.visualid = atoi (override);
vinfo.screen = DefaultScreen (compositor.display);
visuals = XGetVisualInfo (compositor.display,
VisualScreenMask | VisualIDMask,
&vinfo, &nvisuals);
if (!visuals)
return NULL;
if (!nvisuals)
{
XLFree (visuals);
return NULL;
}
/* Now, get the visual and depth, free the visual info, and return
them. */
value = visuals->visual;
/* EGL does not know how to find visuals with an alpha channel, even
if we specify one in the framebuffer configuration. Detect when
that is the case, and pick a better visual. */
value = PickBetterVisual (value, &visuals->depth);
*depth = visuals->depth;
XLFree (visuals);
return value;
}
static Bool
EglPickConfig (void)
{
EGLint egl_config_attribs[20];
EGLint n_configs;
EGLint visual_id;
/* We want the best framebuffer configuration that supports at least
8 bits of red, green, and blue. */
egl_config_attribs[0] = EGL_BUFFER_SIZE;
egl_config_attribs[1] = 24;
egl_config_attribs[2] = EGL_RED_SIZE;
egl_config_attribs[3] = 8;
egl_config_attribs[4] = EGL_GREEN_SIZE;
egl_config_attribs[5] = 8;
egl_config_attribs[6] = EGL_BLUE_SIZE;
egl_config_attribs[7] = 8;
egl_config_attribs[8] = EGL_ALPHA_SIZE;
egl_config_attribs[9] = 0;
/* We want OpenGL ES 2 or later. */
egl_config_attribs[10] = EGL_RENDERABLE_TYPE;
egl_config_attribs[11] = EGL_OPENGL_ES2_BIT;
/* We don't care about the depth or stencil. */
egl_config_attribs[12] = EGL_DEPTH_SIZE;
egl_config_attribs[13] = EGL_DONT_CARE;
egl_config_attribs[14] = EGL_STENCIL_SIZE;
egl_config_attribs[15] = EGL_DONT_CARE;
/* We need support for both windows and pixmap-backed surfaces. */
egl_config_attribs[16] = EGL_SURFACE_TYPE;
egl_config_attribs[17] = EGL_WINDOW_BIT | EGL_PIXMAP_BIT;
/* Terminate the config list. */
egl_config_attribs[18] = EGL_NONE;
/* Now, search for the best matching configuration. */
if (!eglChooseConfig (egl_display, egl_config_attribs,
&egl_config, 1, &n_configs))
/* No config could be found. */
return False;
if (!n_configs)
return False;
/* See if the config has an attached visual ID. */
if (!eglGetConfigAttrib (egl_display, egl_config,
EGL_NATIVE_VISUAL_ID,
&visual_id))
return False;
/* Now, find the visual corresponding to the visual ID. */
compositor.visual = FindVisual (visual_id, &compositor.n_planes);
if (!compositor.visual)
/* The visual couldn't be found. */
return False;
/* Try to find the cursor picture format. */
cursor_format = XRenderFindVisualFormat (compositor.display,
compositor.visual);
/* If no cursor format was found, return False. */
if (!cursor_format)
return False;
/* Otherwise, all of this was set up successfully. */
return True;
}
static Bool
EglCreateContext (void)
{
EGLint attrs[3];
/* Require GLES 2.0. eglBindAPI is not called, so the API used
should be OpenGL ES. */
attrs[0] = EGL_CONTEXT_MAJOR_VERSION;
attrs[1] = 2;
attrs[2] = EGL_NONE;
/* Create the context; if no context is returned, fail. */
egl_context = eglCreateContext (egl_display, egl_config,
EGL_NO_CONTEXT, attrs);
return egl_context != EGL_NO_CONTEXT;
}
static void
CheckShaderCompilation (GLuint shader, const char *name)
{
char msg[1024];
GLint success;
glGetShaderiv (shader, GL_COMPILE_STATUS, &success);
if (success)
return;
glGetShaderInfoLog (shader, sizeof msg, NULL, msg);
/* Report shader compilation error. */
fprintf (stderr, "Failed to compile shader %s: %s\n", name, msg);
abort ();
}
static void
CheckProgramLink (GLuint program, const char *name)
{
char msg[1024];
GLint success;
glGetProgramiv (program, GL_LINK_STATUS, &success);
if (success)
return;
glGetProgramInfoLog (program, sizeof msg, NULL, msg);
/* Report shader compilation error. */
fprintf (stderr, "Failed to link program %s: %s\n", name, msg);
abort ();
}
static void
EglCompileCompositeProgram (CompositeProgram *program,
const char *fragment_shader)
{
GLuint vertex, fragment;
/* There are different composite programs for different
kinds of textures, differing in their fragment shaders. */
vertex = glCreateShader (GL_VERTEX_SHADER);
fragment = glCreateShader (GL_FRAGMENT_SHADER);
glShaderSource (vertex, 1, &composite_rectangle_vertex_shader,
NULL);
glCompileShader (vertex);
CheckShaderCompilation (vertex, "compositor vertex shader");
glShaderSource (fragment, 1, &fragment_shader, NULL);
glCompileShader (fragment);
CheckShaderCompilation (fragment, "compositor fragment shader");
program->program = glCreateProgram ();
glAttachShader (program->program, vertex);
glAttachShader (program->program, fragment);
glLinkProgram (program->program);
CheckProgramLink (program->program, "compositor program");
/* Obtain the indices of the texcoord and pos attributes. */
program->texcoord = glGetAttribLocation (program->program,
"texcoord");
program->position = glGetAttribLocation (program->program,
"pos");
program->texture = glGetUniformLocation (program->program,
"texture");
program->source = glGetUniformLocation (program->program,
"source");
program->invert_y = glGetUniformLocation (program->program,
"invert_y");
program->source_color = glGetUniformLocation (program->program,
"source_color");
/* Now delete the shaders. */
glDeleteShader (vertex);
glDeleteShader (fragment);
}
static void
EglCompileShaders (void)
{
GLuint vertex, fragment;
vertex = glCreateShader (GL_VERTEX_SHADER);
fragment = glCreateShader (GL_FRAGMENT_SHADER);
glShaderSource (vertex, 1, &clear_rectangle_vertex_shader, NULL);
glCompileShader (vertex);
CheckShaderCompilation (vertex, "clear_rectangle_vertex_shader");
glShaderSource (fragment, 1, &clear_rectangle_fragment_shader, NULL);
glCompileShader (fragment);
CheckShaderCompilation (fragment, "clear_rectangle_fragment_shader");
clear_rect_program = glCreateProgram ();
glAttachShader (clear_rect_program, vertex);
glAttachShader (clear_rect_program, fragment);
glLinkProgram (clear_rect_program);
CheckProgramLink (clear_rect_program, "clear_rect_program");
/* Obtain the location of an attribute. */
clear_rect_program_pos_attrib
= glGetAttribLocation (clear_rect_program, "pos");
/* Now delete the shaders. */
glDeleteShader (vertex);
glDeleteShader (fragment);
/* Compile some other programs used for compositing textures. */
EglCompileCompositeProgram (&argb_program,
composite_rectangle_fragment_shader_rgba);
EglCompileCompositeProgram (&xrgb_program,
composite_rectangle_fragment_shader_rgbx);
EglCompileCompositeProgram (&external_program,
composite_rectangle_fragment_shader_external);
EglCompileCompositeProgram (&single_pixel_buffer_program,
composite_rectangle_fragment_shader_single_pixel);
}
/* Forward declaration. */
static void AddRenderFlag (int);
static Bool
EglInitDisplay (void)
{
EGLDisplay *display;
EGLint major, minor;
/* Initialize eglGetPlatformDisplay. */
display = NULL;
EglInitFuncsEarly ();
CheckExtension (IGetPlatformDisplay);
CheckExtension (ICreatePlatformWindowSurface);
CheckExtension (ICreatePlatformPixmapSurface);
/* Then, get the display. */
display = IGetPlatformDisplay (EGL_PLATFORM_X11_KHR, compositor.display,
NULL);
/* Return if the display could not be created. */
if (!display)
return False;
/* Next, try to initialize EGL. */
if (!eglInitialize (display, &major, &minor))
return False;
/* If "EGL_EXT_image_dma_buf_import" is not supported, fail display
initialization. */
egl_display = display;
if (!HaveEglExtension ("EGL_EXT_image_dma_buf_import"))
{
eglTerminate (display);
return False;
}
/* Check if EGL_EXT_buffer_age is supported. */
have_egl_ext_buffer_age = HaveEglExtension ("EGL_EXT_buffer_age");
/* Initialize functions. */
EglInitFuncs ();
CheckExtension (ICreateImage);
CheckExtension (IDestroyImage);
/* If both EGL fences and EGL_ANDROID_native_fence_sync are
supported, enable explicit sync. */
if (ICreateSync && IDupNativeFenceFD)
AddRenderFlag (SupportsExplicitSync);
/* Otherwise, the display has been initialized. */
egl_major = major;
egl_minor = minor;
/* Now, try to pick a framebuffer configuration. */
if (!EglPickConfig ())
{
/* Initializing the framebuffer configuration failed. */
eglTerminate (display);
return False;
}
if (!EglCreateContext ())
{
/* A GL context could not be created. */
eglTerminate (display);
return False;
}
/* Make the display current and initialize GL functions. */
eglMakeCurrent (display, EGL_NO_SURFACE, EGL_NO_SURFACE,
egl_context);
/* This is required for i.e. YUV dma buffer formats. */
CheckGlExtension ("GL_OES_EGL_image_external");
/* This for little endian RGB. */
CheckGlExtension ("GL_EXT_read_format_bgra");
/* This for unpacking subimages. */
CheckGlExtension ("GL_EXT_unpack_subimage");
EglInitGlFuncs ();
CheckExtensionGl (IEGLImageTargetTexture2D);
/* Now, try to compile the shaders. */
EglCompileShaders ();
return True;
}
static Bool
InitRenderFuncs (void)
{
return EglInitDisplay ();
}
static Bool
TryPreserveOnSwap (EGLSurface *surface)
{
EGLint value;
/* Enable preserving the color buffer post eglSwapBuffers. */
eglSurfaceAttrib (egl_display, surface,
EGL_SWAP_BEHAVIOR, EGL_BUFFER_PRESERVED);
value = 0;
/* Check that enabling the attribute was successful. */
if (!eglQuerySurface (egl_display, surface,
EGL_SWAP_BEHAVIOR, &value)
|| value != EGL_BUFFER_PRESERVED)
return False;
return True;
}
static RenderTarget
TargetFromWindow (Window window, unsigned long standard_event_mask)
{
EglTarget *target;
target = XLMalloc (sizeof *target);
target->source = window;
target->flags = 0;
target->surface = ICreatePlatformWindowSurface (egl_display, egl_config,
&window, NULL);
/* Try enabling EGL_BUFFER_PRESERVED to preserve the color buffer
post swap. */
if (TryPreserveOnSwap (target->surface))
target->flags |= SwapPreservesContents;
if (target->surface == EGL_NO_SURFACE)
abort ();
return (RenderTarget) (void *) target;
}
static RenderTarget
TargetFromPixmap (Pixmap pixmap)
{
EglTarget *target;
target = XLMalloc (sizeof *target);
target->source = pixmap;
target->flags = 0;
target->surface = ICreatePlatformPixmapSurface (egl_display, egl_config,
&pixmap, NULL);
/* Mark the target as being a pixmap surface. EGL pixmap surfaces
are always single-buffered, so we have to call glFinish
manually. */
target->flags |= IsPixmap;
/* Try enabling EGL_BUFFER_PRESERVED to preserve the color buffer
post swap. */
if (TryPreserveOnSwap (target->surface))
target->flags |= SwapPreservesContents;
if (target->surface == EGL_NO_SURFACE)
abort ();
return (RenderTarget) (void *) target;
}
static void
SetStandardEventMask (RenderTarget target, unsigned long standard_event_mask)
{
/* Ignored. */
}
static void
NoteTargetSize (RenderTarget target, int width, int height)
{
EglTarget *egl_target;
egl_target = target.pointer;
/* This really ought to fit in unsigned short... */
egl_target->width = width;
egl_target->height = height;
}
static Picture
PictureFromTarget (RenderTarget target)
{
EglTarget *egl_target;
XRenderPictureAttributes picture_attrs;
/* This is just to pacify GCC; picture_attrs is not used as mask is
0. */
memset (&picture_attrs, 0, sizeof picture_attrs);
egl_target = target.pointer;
return XRenderCreatePicture (compositor.display,
egl_target->source,
cursor_format, 0,
&picture_attrs);
}
static void
FreePictureFromTarget (Picture picture)
{
XRenderFreePicture (compositor.display, picture);
}
static void
DestroyRenderTarget (RenderTarget target)
{
EglTarget *egl_target;
egl_target = target.pointer;
/* Destroy the EGL surface. */
eglDestroySurface (egl_display, egl_target->surface);
/* If the target is current, clear the current target. */
if (egl_target == current_target)
{
current_target = NULL;
/* Make the context current with no surface. */
eglMakeCurrent (egl_display, EGL_NO_SURFACE,
EGL_NO_SURFACE, egl_context);
}
/* Free the target object. */
XLFree (egl_target);
}
static void
MakeRenderTargetCurrent (RenderTarget target)
{
EglTarget *egl_target;
if (target.pointer == current_target)
/* The target is already current. */
return;
egl_target = target.pointer;
/* Otherwise, make it current for the context. */
if (!eglMakeCurrent (egl_display, egl_target->surface,
egl_target->surface, egl_context))
abort ();
current_target = egl_target;
/* Set the swap interval to 0 - we use _NET_WM_SYNC_REQUEST for
synchronization. */
eglSwapInterval (egl_display, 0);
/* Specify clear color for the color buffer. We want the color
buffer to be completely transparent when clear. */
glClearColor (0, 0, 0, 0);
}
static void
StartRender (RenderTarget target)
{
EglTarget *egl_target;
MakeRenderTargetCurrent (target);
egl_target = target.pointer;
/* Set the viewport. */
glViewport (0, 0, egl_target->width,
egl_target->height);
/* Also set the blend function to one that makes sense. */
glBlendFunc (GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
}
static void
FillBoxesWithTransparency (RenderTarget target, pixman_box32_t *boxes,
int nboxes, int min_x, int min_y)
{
GLfloat *verts;
EglTarget *egl_target;
int i;
GLfloat x1, x2, y1, y2;
egl_target = target.pointer;
glDisable (GL_BLEND);
glUseProgram (clear_rect_program);
/* Allocate enough to hold each triangle. */
verts = alloca (sizeof *verts * nboxes * 8);
for (i = 0; i < nboxes; ++i)
{
/* Translate the coordinates by the min_x and min_y. */
x1 = boxes[i].x1 - min_x;
x2 = boxes[i].x2 - min_x;
y1 = boxes[i].y1 - min_y;
y2 = boxes[i].y2 - min_y;
/* Bottom left. */
verts[i * 8 + 0] = -1.0f + x1 / egl_target->width * 2;
verts[i * 8 + 1] = -1.0f + (egl_target->height - y2) / egl_target->height * 2;
/* Top left. */
verts[i * 8 + 2] = -1.0f + x1 / egl_target->width * 2;
verts[i * 8 + 3] = -1.0f + (egl_target->height - y1) / egl_target->height * 2;
/* Bottom right. */
verts[i * 8 + 4] = -1.0f + x2 / egl_target->width * 2;
verts[i * 8 + 5] = -1.0f + (egl_target->height - y2) / egl_target->height * 2;
/* Top right. */
verts[i * 8 + 6] = -1.0f + x2 / egl_target->width * 2;
verts[i * 8 + 7] = -1.0f + (egl_target->height - y1) / egl_target->height * 2;
}
/* Upload the verts. */
glVertexAttribPointer (clear_rect_program_pos_attrib,
2, GL_FLOAT, GL_FALSE, 0, verts);
glEnableVertexAttribArray (clear_rect_program_pos_attrib);
for (i = 0; i < nboxes; ++i)
/* Draw each rectangle. */
glDrawArrays (GL_TRIANGLE_STRIP, i * 4, 4);
glDisableVertexAttribArray (clear_rect_program_pos_attrib);
}
static void
ClearRectangle (RenderTarget target, int x, int y, int width, int height)
{
pixman_box32_t box;
box.x1 = x;
box.x2 = x + width;
box.y1 = y;
box.y2 = y + height;
FillBoxesWithTransparency (target, &box, 1, 0, 0);
}
static CompositeProgram *
FindProgram (EglBuffer *buffer)
{
switch (buffer->u.type)
{
case SinglePixelBuffer:
/* Use the single-pixel buffer program. */
return &single_pixel_buffer_program;
case DmaBufBuffer:
if (buffer->u.dmabuf.format->flags & NeedExternalTarget)
/* Use the external format compositor program. */
return &external_program;
Fallthrough;
case ShmBuffer:
default:
/* Otherwise, return the ARGB or XRGB program depending on
whether or not an alpha channel is present. */
return (buffer->flags & HasAlpha
? &argb_program : &xrgb_program);
}
}
static GLenum
GetTextureTarget (EglBuffer *buffer)
{
switch (buffer->u.type)
{
case DmaBufBuffer:
return (buffer->u.dmabuf.format->flags & NeedExternalTarget
? GL_TEXTURE_EXTERNAL_OES : GL_TEXTURE_2D);
case ShmBuffer:
return GL_TEXTURE_2D;
default:
/* This should not be called with a single pixel buffer. */
abort ();
}
/* This is not supposed to happen. */
abort ();
}
static void
ComputeTransformMatrix (EglBuffer *buffer, DrawParams *params)
{
/* Update the transformation matrix of BUFFER. This is a 3x3
transformation matrix that maps from texcoords to actual
coordinates in the buffer. */
/* Copy over the identity transform. */
MatrixIdentity (&buffer->matrix);
/* Set the X and Y scales. */
if (params->flags & ScaleSet)
{
Index (buffer->matrix, 0, 0)
= (float) (1.0 / params->scale);
Index (buffer->matrix, 1, 1)
= (float) (1.0 / params->scale);
}
/* Set the rotation. */
if (params->flags & TransformSet)
{
/* Apply the inverse transform. */
ApplyInverseTransform (1, 1, &buffer->matrix,
params->transform);
/* Since the rotation happened inside the texture coordinate
system, scale u and v correctly if dimensions changed. */
if (RotatesDimensions (params->transform))
MatrixScale (&buffer->matrix, ((float) buffer->width
/ (float) buffer->height),
((float) buffer->height
/ (float) buffer->width));
}
/* Set the offsets. */
if (params->flags & OffsetSet)
MatrixTranslate (&buffer->matrix,
(float) (params->off_x / buffer->width),
(float) (params->off_y / buffer->height));
/* Set the stretch. */
if (params->flags & StretchSet)
/* Scale the buffer down by this much. */
MatrixScale (&buffer->matrix,
(float) (params->crop_width / params->stretch_width),
(float) (params->crop_height / params->stretch_height));
}
/* Forward declaration. */
static void EnsureTexture (EglBuffer *);
static void
Composite (RenderBuffer buffer, RenderTarget target,
Operation op, int src_x, int src_y, int x, int y,
int width, int height, DrawParams *params)
{
GLfloat verts[8], texcoord[8];
GLfloat x1, x2, y1, y2;
EglTarget *egl_target;
EglBuffer *egl_buffer;
CompositeProgram *program;
GLenum tex_target;
egl_target = target.pointer;
egl_buffer = buffer.pointer;
if (egl_buffer->u.type != SinglePixelBuffer)
{
/* If no texture was generated, upload the buffer contents
now. */
if (!(egl_buffer->flags & IsTextureGenerated))
EnsureTexture (egl_buffer);
/* Get the texturing target. */
tex_target = GetTextureTarget (egl_buffer);
}
else
/* This value is not actually used. */
tex_target = 0;
/* Find the program to use for compositing. */
program = FindProgram (egl_buffer);
/* Compute the transformation matrix to use to draw the given
buffer. */
ComputeTransformMatrix (egl_buffer, params);
/* dest rectangle on target. */
x1 = x;
y1 = y;
x2 = x + width;
y2 = y + height;
/* Bottom left. */
verts[0] = -1.0f + x1 / egl_target->width * 2;
verts[1] = -1.0f + (egl_target->height - y2) / egl_target->height * 2;
/* Top left. */
verts[2] = -1.0f + x1 / egl_target->width * 2;
verts[3] = -1.0f + (egl_target->height - y1) / egl_target->height * 2;
/* Bottom right. */
verts[4] = -1.0f + x2 / egl_target->width * 2;
verts[5] = -1.0f + (egl_target->height - y2) / egl_target->height * 2;
/* Top right. */
verts[6] = -1.0f + x2 / egl_target->width * 2;
verts[7] = -1.0f + (egl_target->height - y1) / egl_target->height * 2;
/* source rectangle on buffer. */
x1 = src_x;
y1 = src_y;
x2 = src_x + width;
y2 = src_y + height;
texcoord[0] = x1 / egl_buffer->width;
texcoord[1] = y2 / egl_buffer->height;
texcoord[2] = x1 / egl_buffer->width;
texcoord[3] = y1 / egl_buffer->height;
texcoord[4] = x2 / egl_buffer->width;
texcoord[5] = y2 / egl_buffer->height;
texcoord[6] = x2 / egl_buffer->width;
texcoord[7] = y1 / egl_buffer->height;
/* Disable blending based on whether or not an alpha channel is
present. */
if (op == OperationOver
&& egl_buffer->flags & HasAlpha)
glEnable (GL_BLEND);
else
glDisable (GL_BLEND);
/* Single pixel buffers have no textures. */
if (egl_buffer->u.type != SinglePixelBuffer)
{
glActiveTexture (GL_TEXTURE0);
glBindTexture (tex_target, egl_buffer->texture);
glTexParameteri (tex_target, GL_TEXTURE_MIN_FILTER,
GL_NEAREST);
glTexParameteri (tex_target, GL_TEXTURE_MAG_FILTER,
GL_NEAREST);
}
glUseProgram (program->program);
/* Single pixel buffers have no textures. */
if (egl_buffer->u.type != SinglePixelBuffer)
glUniform1i (program->texture, 0);
else
/* Attach the source color. */
glUniform4f (program->source_color,
egl_buffer->u.single_pixel.r,
egl_buffer->u.single_pixel.g,
egl_buffer->u.single_pixel.b,
egl_buffer->u.single_pixel.a);
glUniformMatrix3fv (program->source, 1, GL_FALSE,
egl_buffer->matrix);
glUniform1i (program->invert_y, egl_buffer->flags & InvertY);
glVertexAttribPointer (program->position, 2, GL_FLOAT,
GL_FALSE, 0, verts);
glVertexAttribPointer (program->texcoord, 2, GL_FLOAT,
GL_FALSE, 0, texcoord);
glEnableVertexAttribArray (program->position);
glEnableVertexAttribArray (program->texcoord);
glDrawArrays (GL_TRIANGLE_STRIP, 0, 4);
glDisableVertexAttribArray (program->position);
glDisableVertexAttribArray (program->texcoord);
/* Single pixel buffers have no textures. */
if (egl_buffer->u.type != SinglePixelBuffer)
glBindTexture (tex_target, 0);
}
static RenderCompletionKey
FinishRender (RenderTarget target, pixman_region32_t *damage,
RenderCompletionFunc callback, void *data)
{
EglTarget *egl_target;
EGLint *rects;
int nboxes, i;
pixman_box32_t *boxes;
egl_target = target.pointer;
if (egl_target->flags & IsPixmap)
glFinish ();
else if (!ISwapBuffersWithDamage || !damage)
/* This should also do glFinish. */
eglSwapBuffers (egl_display, egl_target->surface);
else
{
/* Do a swap taking the buffer damage into account. First,
convert the damage into cartesian coordinates. */
boxes = pixman_region32_rectangles (damage, &nboxes);
rects = alloca (nboxes * 4 * sizeof *damage);
for (i = 0; i < nboxes; ++i)
{
rects[i * 4 + 0] = boxes[i].x1;
rects[i * 4 + 1] = egl_target->height - boxes[i].y2;
rects[i * 4 + 2] = boxes[i].x2 - boxes[i].x1;
rects[i * 4 + 3] = boxes[i].y2 - boxes[i].y1;
}
/* Next, swap buffers with the damage. */
ISwapBuffersWithDamage (egl_display, egl_target->surface,
rects, nboxes);
}
return NULL;
}
static int
TargetAge (RenderTarget target)
{
EglTarget *egl_target;
EGLint age;
egl_target = target.pointer;
/* If egl_target->flags & SwapPreservesContents, return 0. */
if (egl_target->flags & SwapPreservesContents)
return 0;
/* Otherwise, return <age of buffer> - 1. */
if (have_egl_ext_buffer_age
&& eglQuerySurface (egl_display, egl_target->surface,
EGL_BUFFER_AGE_EXT, &age))
return age - 1;
/* Fall back to -1 if obtaining the buffer age failed. */
return -1;
}
static RenderFence
ImportFdFence (int fd, Bool *error)
{
EGLSyncKHR *fence;
EGLint attribs[3];
attribs[0] = EGL_SYNC_NATIVE_FENCE_FD_ANDROID;
attribs[1] = fd;
attribs[2] = EGL_NONE;
/* This fence is supposed to assume ownership over the given file
descriptor. */
fence = ICreateSync (egl_display, EGL_SYNC_NATIVE_FENCE_ANDROID,
attribs);
if (fence == EGL_NO_SYNC_KHR)
{
*error = True;
return (RenderFence) NULL;
}
return (RenderFence) (void *) fence;
}
static void
WaitFence (RenderFence fence)
{
/* N.B. that here egl_context must be current, which should always
be true. */
if (IWaitSync)
/* This is more asynchronous, as it doesn't wait for the fence
on the CPU. */
IWaitSync (egl_display, fence.pointer, 0);
else
/* But eglWaitSyncKHR isn't available everywhere. */
IClientWaitSync (egl_display, fence.pointer, 0,
EGL_FOREVER_KHR);
/* If either of these requests fail, simply proceed to read from the
protected data. */
}
static void
DeleteFence (RenderFence fence)
{
if (!IDestroySync (egl_display, fence.pointer))
/* There is no way to continue without leaking memory, and this
shouldn't happen. */
abort ();
}
static void
HandleFenceReadable (int fd, void *data, ReadFd *readfd)
{
XLRemoveReadFd (readfd);
/* Now destroy the native fence. */
if (!IDestroySync (egl_display, data))
abort ();
/* And close the file descriptor. */
close (fd);
}
static int
GetFinishFence (Bool *error)
{
EGLint attribs;
EGLSyncKHR *fence;
EGLint fd;
attribs = EGL_NONE;
/* Create the fence. EGL_SYNC_CONDITION_KHR should default to
EGL_SYNC_PRIOR_COMMANDS_COMPLETE_KHR, meaning it will signal once
all prior drawing commands complete. */
fence = ICreateSync (egl_display, EGL_SYNC_NATIVE_FENCE_ANDROID,
&attribs);
if (fence == EGL_NO_SYNC_KHR)
{
*error = True;
return -1;
}
/* Obtain the file descriptor. */
fd = IDupNativeFenceFD (egl_display, fence);
if (fd == -1)
*error = True;
else
/* Delete the fence after it is signalled. Duplicate the fd, as
it will be closed by the caller. */
XLAddReadFd (dup (fd), fence, HandleFenceReadable);
return fd;
}
static RenderFuncs egl_render_funcs =
{
.init_render_funcs = InitRenderFuncs,
.target_from_window = TargetFromWindow,
.target_from_pixmap = TargetFromPixmap,
.set_standard_event_mask = SetStandardEventMask,
.note_target_size = NoteTargetSize,
.picture_from_target = PictureFromTarget,
.free_picture_from_target = FreePictureFromTarget,
.destroy_render_target = DestroyRenderTarget,
.start_render = StartRender,
.fill_boxes_with_transparency = FillBoxesWithTransparency,
.clear_rectangle = ClearRectangle,
.composite = Composite,
.finish_render = FinishRender,
.target_age = TargetAge,
.import_fd_fence = ImportFdFence,
.wait_fence = WaitFence,
.delete_fence = DeleteFence,
.get_finish_fence = GetFinishFence,
.flags = ImmediateRelease,
};
static void
AddRenderFlag (int flags)
{
egl_render_funcs.flags |= flags;
}
static DrmFormat *
GetDrmFormats (int *num_formats)
{
*num_formats = n_drm_formats;
return drm_formats;
}
static dev_t *
GetRenderDevices (int *num_devices)
{
if (!drm_device_available)
{
*num_devices = 0;
return NULL;
}
*num_devices = 1;
return &drm_device;
}
static ShmFormat *
GetShmFormats (int *num_formats)
{
*num_formats = n_shm_formats;
return shm_formats;
}
static DrmFormat *
FindDrmFormat (uint32_t format, uint64_t modifier)
{
int i;
/* Find the DRM format associated with FORMAT and MODIFIER. This is
mainly used to extract flags. */
for (i = 0; i < n_drm_formats; ++i)
{
if (drm_formats[i].drm_format == format
&& drm_formats[i].drm_modifier == modifier)
return &drm_formats[i];
}
return NULL;
}
static void
CloseFileDescriptors (DmaBufAttributes *attributes)
{
int i;
for (i = 0; i < attributes->n_planes; ++i)
close (attributes->fds[i]);
}
static FormatInfo *
FindFormatInfoDrm (uint32_t drm_format)
{
int i;
for (i = 0; i < ArrayElements (known_shm_formats); ++i)
{
if (known_shm_formats[i].drm_format == drm_format)
return &known_shm_formats[i];
}
return NULL;
}
static RenderBuffer
BufferFromDmaBuf (DmaBufAttributes *attributes, Bool *error)
{
EglBuffer *buffer;
DrmFormat *format;
EGLint attribs[50], i;
FormatInfo *info;
buffer = XLMalloc (EglBufferSize (EglDmaBufBuffer));
buffer->flags = 0;
buffer->texture = EGL_NO_TEXTURE;
buffer->width = attributes->width;
buffer->height = attributes->height;
buffer->u.type = DmaBufBuffer;
/* Copy over the identity transform. */
MatrixIdentity (&buffer->matrix);
i = 0;
/* Find the DRM format in question so we can determine the right
target to use. */
format = FindDrmFormat (attributes->drm_format,
attributes->modifier);
XLAssert (format != NULL);
/* Find the corresponding FormatInfo record, to determine whether or
not an alpha channel is present. If it cannot be found, no
problems there - just assume there is an alpha channel. */
info = FindFormatInfoDrm (attributes->drm_format);
if (!info || info->has_alpha)
buffer->flags |= HasAlpha;
/* If modifiers were specified and are not supported, fail. */
if (!IQueryDmaBufModifiers
&& attributes->modifier != DRM_FORMAT_MOD_INVALID)
goto error;
/* Set invert_y based on flags. */
if (attributes->flags
& ZWP_LINUX_BUFFER_PARAMS_V1_FLAGS_Y_INVERT)
buffer->flags |= InvertY;
/* Otherwise, import the buffer now. */
attribs[i++] = EGL_WIDTH;
attribs[i++] = attributes->width;
attribs[i++] = EGL_HEIGHT;
attribs[i++] = attributes->height;
attribs[i++] = EGL_LINUX_DRM_FOURCC_EXT;
attribs[i++] = attributes->drm_format;
/* Note that the file descriptors have to be closed in any case. */
#define LoadPlane(no) \
{ \
attribs[i++] = EGL_DMA_BUF_PLANE ## no ## _FD_EXT; \
attribs[i++] = attributes->fds[no]; \
attribs[i++] = EGL_DMA_BUF_PLANE ## no ## _OFFSET_EXT; \
attribs[i++] = attributes->offsets[no]; \
attribs[i++] = EGL_DMA_BUF_PLANE ## no ## _PITCH_EXT; \
attribs[i++] = attributes->strides[no]; \
\
/* Set modifiers if the invalid modifier is not being used. */ \
\
if (IQueryDmaBufModifiers \
&& attributes->modifier != DRM_FORMAT_MOD_INVALID) \
{ \
attribs[i++] = EGL_DMA_BUF_PLANE ## no ## _MODIFIER_LO_EXT; \
attribs[i++] = attributes->modifier & 0xffffffff; \
attribs[i++] = EGL_DMA_BUF_PLANE ## no ## _MODIFIER_HI_EXT; \
attribs[i++] = attributes->modifier >> 32; \
} \
}
/* Load plane 0. There is always at least one plane. */
LoadPlane (0);
/* Next, load each plane specified. */
if (attributes->n_planes > 1)
LoadPlane (1);
if (attributes->n_planes > 2)
LoadPlane (2);
if (attributes->n_planes > 3)
LoadPlane (3);
#undef LoadPlane
/* Make sure the pixel data is preserved. */
attribs[i++] = EGL_IMAGE_PRESERVED_KHR;
attribs[i++] = EGL_TRUE;
/* Terminate the attribute list. */
attribs[i++] = EGL_NONE;
/* Create the image. */
buffer->u.dmabuf.image = ICreateImage (egl_display, EGL_NO_CONTEXT,
EGL_LINUX_DMA_BUF_EXT, NULL,
attribs);
/* Check that no error occured. */
if (buffer->u.dmabuf.image == EGL_NO_IMAGE)
goto error;
/* Set the DRM format used to create this image. */
buffer->u.dmabuf.format = format;
/* Close file descriptors and return the image. */
CloseFileDescriptors (attributes);
return (RenderBuffer) (void *) buffer;
error:
CloseFileDescriptors (attributes);
XLFree (buffer);
*error = True;
return (RenderBuffer) NULL;
}
static void
BufferFromDmaBufAsync (DmaBufAttributes *attributes,
DmaBufSuccessFunc success_callback,
DmaBufFailureFunc failure_callback,
void *callback_data)
{
Bool error;
RenderBuffer buffer;
error = False;
buffer = BufferFromDmaBuf (attributes, &error);
if (error)
failure_callback (callback_data);
else
success_callback (buffer, callback_data);
}
static FormatInfo *
FindFormatInfo (uint32_t wl_format)
{
int i;
for (i = 0; i < ArrayElements (known_shm_formats); ++i)
{
if (known_shm_formats[i].wl_format == wl_format)
return &known_shm_formats[i];
}
return NULL;
}
static RenderBuffer
BufferFromShm (SharedMemoryAttributes *attributes, Bool *error)
{
EglBuffer *buffer;
buffer = XLMalloc (EglBufferSize (EglShmBuffer));
buffer->flags = 0;
buffer->texture = EGL_NO_TEXTURE;
buffer->width = attributes->width;
buffer->height = attributes->height;
buffer->u.type = ShmBuffer;
/* Copy over the identity transform. */
MatrixIdentity (&buffer->matrix);
/* Record the buffer data. */
buffer->u.shm.format = FindFormatInfo (attributes->format);
XLAssert (buffer->u.shm.format != NULL);
buffer->u.shm.offset = attributes->offset;
buffer->u.shm.stride = attributes->stride;
buffer->u.shm.data = attributes->data;
/* Record whether or not the format supports an alpha channel. */
if (buffer->u.shm.format->has_alpha)
buffer->flags |= HasAlpha;
/* Return the buffer. */
return (RenderBuffer) (void *) buffer;
}
static RenderBuffer
BufferFromSinglePixel (uint32_t red, uint32_t green, uint32_t blue,
uint32_t alpha, Bool *error)
{
EglBuffer *buffer;
buffer = XLMalloc (EglBufferSize (EglSinglePixelBuffer));
buffer->flags = 0;
buffer->texture = EGL_NO_TEXTURE;
buffer->width = 1;
buffer->height = 1;
buffer->u.type = SinglePixelBuffer;
/* Copy over the identity transform. */
MatrixIdentity (&buffer->matrix);
/* Record the buffer data. */
buffer->u.single_pixel.r = red / (float) 0xffffffff;
buffer->u.single_pixel.g = green / (float) 0xffffffff;
buffer->u.single_pixel.b = blue / (float) 0xffffffff;
buffer->u.single_pixel.a = alpha / (float) 0xffffffff;
/* An alpha channel is present. */
if (buffer->u.single_pixel.a < 1)
buffer->flags |= HasAlpha;
/* Return the buffer. */
return (RenderBuffer) (void *) buffer;
}
static void
FreeShmBuffer (RenderBuffer buffer)
{
EglBuffer *egl_buffer;
egl_buffer = buffer.pointer;
/* If a texture is attached, delete it. */
if (egl_buffer->flags & IsTextureGenerated)
glDeleteTextures (1, &egl_buffer->texture);
XLFree (buffer.pointer);
}
static void
FreeDmabufBuffer (RenderBuffer buffer)
{
EglBuffer *egl_buffer;
egl_buffer = buffer.pointer;
/* If a texture is attached, delete it. */
if (egl_buffer->flags & IsTextureGenerated)
glDeleteTextures (1, &egl_buffer->texture);
/* Free the EGL image. */
IDestroyImage (egl_display, egl_buffer->u.dmabuf.image);
XLFree (buffer.pointer);
}
static void
FreeSinglePixelBuffer (RenderBuffer buffer)
{
EglBuffer *egl_buffer;
egl_buffer = buffer.pointer;
/* Make sure a texture was not created. */
XLAssert (egl_buffer->texture == EGL_NO_TEXTURE);
/* Free the wrapper struct. */
XLFree (buffer.pointer);
}
/* Initialization functions. */
static void
AddDrmFormat (uint32_t format, uint64_t modifier,
int flags)
{
/* First, make drm_formats big enough. */
drm_formats
= XLRealloc (drm_formats,
++n_drm_formats * sizeof *drm_formats);
/* Then, write the format to the end. */
drm_formats[n_drm_formats - 1].drm_format = format;
drm_formats[n_drm_formats - 1].drm_modifier = modifier;
drm_formats[n_drm_formats - 1].flags = flags;
}
static void
InitModifiersFor (uint32_t format)
{
EGLuint64KHR *modifiers;
EGLint i, n_total_modifiers;
EGLBoolean *external_only;
int flags;
/* First, look up how many modifiers are supported for the given
format. */
IQueryDmaBufModifiers (egl_display, format, 0, NULL,
NULL, &n_total_modifiers);
/* Next, allocate a buffer that can hold that many modifiers. */
modifiers = alloca (n_total_modifiers * sizeof *modifiers);
external_only = alloca (n_total_modifiers * sizeof *modifiers);
/* And query the modifiers for real. */
IQueryDmaBufModifiers (egl_display, format, n_total_modifiers,
modifiers, external_only, &n_total_modifiers);
/* Add each modifier. */
for (i = 0; i < n_total_modifiers; ++i)
{
/* If the modifier requires GL_TEXTURE_EXTERNAL_OES, specify
that. */
flags = external_only[i] ? NeedExternalTarget : 0;
/* And add the DRM format. */
AddDrmFormat (format, modifiers[i], flags);
}
}
static void
InitDmaBufFormats (void)
{
static DrmFormat fallback_formats[2];
EGLint i, n_total_formats, *formats;
if (!IQueryDmaBufModifiers)
{
/* If the import_modifiers extension isn't supported, there's no
way to query for supported formats. Return a few formats
that should probably be supported everywhere. */
fallback_formats[0].drm_format = DRM_FORMAT_ARGB8888;
fallback_formats[0].drm_modifier = DRM_FORMAT_MOD_INVALID;
fallback_formats[1].drm_format = DRM_FORMAT_XRGB8888;
fallback_formats[1].drm_modifier = DRM_FORMAT_MOD_INVALID;
drm_formats = fallback_formats;
n_drm_formats = 2;
return;
}
/* Otherwise, look up what is supported. First, check how many
formats are supported. */
IQueryDmaBufFormats (egl_display, 0, NULL, &n_total_formats);
/* Next, allocate a buffer that can hold that many formats. */
formats = alloca (n_total_formats * sizeof *formats);
/* And query the formats for real. */
IQueryDmaBufFormats (egl_display, n_total_formats, formats,
&n_total_formats);
for (i = 0; i < n_total_formats; ++i)
{
/* Now, add the implicit modifier. */
AddDrmFormat (formats[i], DRM_FORMAT_MOD_INVALID, 0);
/* Next, query for and add each supported modifier. */
InitModifiersFor (formats[i]);
}
}
static void
AddShmFormat (uint32_t format)
{
shm_formats
= XLRealloc (shm_formats, (sizeof *shm_formats
* ++n_shm_formats));
shm_formats[n_shm_formats - 1].format = format;
}
static void
InitShmFormats (void)
{
int i;
/* Add formats always supported by GL. */
for (i = 0; i < ArrayElements (known_shm_formats); ++i)
AddShmFormat (known_shm_formats[i].wl_format);
}
static void
InitBufferFuncs (void)
{
EGLAttrib attrib;
EGLDeviceEXT *device;
const char *extensions, *name;
struct stat dev_stat;
/* Try to obtain the device name of a DRM node used to create the
EGL display. First, we try to look for render nodes, but settle
for master nodes if render nodes are not available. */
if (IQueryDisplayAttrib && IQueryDeviceString)
{
if (!IQueryDisplayAttrib (egl_display, EGL_DEVICE_EXT, &attrib))
goto failed;
/* Get the EGLDevice object. */
device = (EGLDeviceEXT) attrib;
/* Get extensions supported by the device. */
extensions = IQueryDeviceString (device, EGL_EXTENSIONS);
if (!extensions)
goto failed;
/* Now, get the path to the render device. */
name = NULL;
if (HaveEglExtension1 (extensions, "EGL_EXT_device_drm_render_node"))
name = IQueryDeviceString (device, EGL_DRM_RENDER_NODE_FILE_EXT);
if (!name && HaveEglExtension1 (extensions, "EGL_EXT_device_drm"))
name = IQueryDeviceString (device, EGL_DRM_DEVICE_FILE_EXT);
if (!name)
goto failed;
if (stat (name, &dev_stat) != 0)
goto failed;
if (!dev_stat.st_rdev)
goto failed;
drm_device = dev_stat.st_rdev;
drm_device_available = True;
}
else
failed:
fprintf (stderr, "Warning: failed to obtain device node of"
" EGL display. Hardware acceleration will probably not"
" be available.\n");
/* Now, initialize the dmabuf formats that are supported. */
InitDmaBufFormats ();
/* And initialize the SHM formats that are supported. */
InitShmFormats ();
/* TODO: remove this message. */
fprintf (stderr, "EGL initialization complete.\n");
}
static Bool
ValidateShmParams (uint32_t format, uint32_t width, uint32_t height,
int32_t offset, int32_t stride, size_t pool_size)
{
size_t total, after, min_stride;
FormatInfo *info;
if (stride < 0 || offset < 0)
/* Return False if any signed values are less than 0. */
return False;
/* Calculate the total size of the buffer, and make sure it is
smaller than the pool size. */
if (IntMultiplyWrapv ((size_t) height, stride, &total))
/* If obtaining the total size would overflow size_t, return. */
return False;
/* If the total size + offset is larger than the pool size,
return. */
if (IntAddWrapv (offset, total, &after)
|| after > pool_size)
return False;
/* Get the format info. */
info = FindFormatInfo (format);
if (info == NULL)
/* This isn't supposed to happen but pacifies
-Wanalyzer-null-dereference. */
return False;
/* If the stride is not enough to hold width, return. */
if (IntMultiplyWrapv ((size_t) width, info->bpp / 8, &min_stride)
|| stride < min_stride
/* If stride is not a multiple of the pixel size, return. */
|| stride % (info->bpp / 8))
return False;
/* The dimensions are valid. */
return True;
}
static void
GetShmParams (EglBuffer *buffer, void **data_ptr, size_t *expected_size)
{
char *pool_data;
/* The end of a buffer is as follows: pool data + offset + stride *
height. The following assumptions must also hold true: stride >=
format->bpp / 8 * buffer->width, offset + size < pool size. */
pool_data = *buffer->u.shm.data;
*data_ptr = pool_data + buffer->u.shm.offset;
*expected_size = (size_t) buffer->u.shm.stride * buffer->height;
}
static void
UpdateTexture (EglBuffer *buffer)
{
GLenum target;
void *data_ptr;
size_t expected_data_size;
/* Get the appropriate target for the texture. */
target = GetTextureTarget (buffer);
/* Bind the target to the texture. */
glBindTexture (target, buffer->texture);
/* Set the wrapping mode to CLAMP_TO_EDGE. */
glTexParameteri (target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri (target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
switch (buffer->u.type)
{
case DmaBufBuffer:
/* This is easy. Simply bind the EGL image to the target. */
IEGLImageTargetTexture2D (target, buffer->u.dmabuf.image);
break;
case ShmBuffer:
/* This is much more complicated... First, set the row length to
the stride. */
glPixelStorei (GL_UNPACK_ROW_LENGTH_EXT,
buffer->u.shm.stride / (buffer->u.shm.format->bpp / 8));
/* Compute the expected data size and data pointer of the
buffer. This is only valid until the next time ResizePool is
called. */
GetShmParams (buffer, &data_ptr, &expected_data_size);
/* Next, specify the 2D image. */
glTexImage2D (target, 0, (buffer->u.shm.format->gl_internalformat
? buffer->u.shm.format->gl_internalformat
: buffer->u.shm.format->gl_format),
buffer->width, buffer->height, 0,
buffer->u.shm.format->gl_format,
buffer->u.shm.format->gl_type, data_ptr);
/* Unset the row length. */
glPixelStorei (GL_UNPACK_ROW_LENGTH_EXT, 0);
/* The buffer's been copied to the texture. It can now be
released. */
buffer->flags |= CanRelease;
break;
default:
break;
}
/* Bind the target to nothing. */
glBindTexture (target, 0);
}
static void
ReverseTransformToBox (DrawParams *params, pixman_box32_t *box)
{
double x_factor, y_factor;
if (!params)
return;
/* Apply the inverse of PARAMS to BOX, for use in damage
tracking. */
if (params->flags & ScaleSet)
{
box->x1 = floor (box->x1 / params->scale);
box->y1 = floor (box->y1 / params->scale);
box->x2 = ceil (box->x2 / params->scale);
box->y2 = ceil (box->y2 / params->scale);
}
if (params->flags & OffsetSet)
{
/* Since the offset can be a fractional value, also try to
include as much as possible in the box. */
box->x1 = floor (box->x1 + params->off_x);
box->y1 = floor (box->y1 + params->off_y);
box->x2 = ceil (box->x2 + params->off_x);
box->y2 = ceil (box->y2 + params->off_y);
}
if (params->flags & StretchSet)
{
x_factor = params->crop_width / params->stretch_width;
y_factor = params->crop_height / params->stretch_height;
box->x1 = floor (box->x1 * x_factor);
box->y1 = floor (box->y1 * y_factor);
box->x2 = ceil (box->x2 * x_factor);
box->y2 = ceil (box->y2 * y_factor);
}
}
static void
UpdateShmBufferIncrementally (EglBuffer *buffer, pixman_region32_t *damage,
DrawParams *params)
{
GLenum target;
pixman_box32_t *boxes, box;
int nboxes, i, width, height;
void *data_ptr;
size_t expected_data_size;
/* Obtain the rectangles that are part of the damage. */
boxes = pixman_region32_rectangles (damage, &nboxes);
if (!nboxes)
return;
/* Compute the expected data size and data pointer of the buffer.
This is only valid until the next time ResizePool is called. */
GetShmParams (buffer, &data_ptr, &expected_data_size);
/* Get the texturing target. */
target = GetTextureTarget (buffer);
/* Bind the target to the texture. */
glBindTexture (target, buffer->texture);
/* And copy from the shm data to the texture according to
the damage. */
for (i = 0; i < nboxes; ++i)
{
/* Get a copy of the box. */
box = boxes[i];
/* Transform the box according to any transforms. */
ReverseTransformToBox (params, &box);
/* Clip the box X and Y to 0, 0. */
box.x1 = MIN (box.y1, 0);
box.y1 = MIN (box.y1, 0);
/* These computations are correct, since box->x2/box->y2 are
actually 1 pixel outside the last pixel in the box. */
width = MIN (box.x2, buffer->width) - box.x1;
height = MIN (box.y2, buffer->height) - box.y1;
if (width <= 0 || height <= 0)
/* The box is effectively empty because it straddles one of
the corners of the buffer, or is outside of the buffer. */
continue;
/* First, set the length of a single row. */
glPixelStorei (GL_UNPACK_ROW_LENGTH_EXT,
buffer->u.shm.stride / (buffer->u.shm.format->bpp / 8));
/* Next, skip box.x1 pixels of each row. */
glPixelStorei (GL_UNPACK_SKIP_PIXELS_EXT, box.x1);
/* And box.y1 rows. */
glPixelStorei (GL_UNPACK_SKIP_ROWS_EXT, box.y1);
/* Copy the image into the sub-texture. */
glTexSubImage2D (target, 0, box.x1, box.y1, width, height,
buffer->u.shm.format->gl_format,
buffer->u.shm.format->gl_type, data_ptr);
}
/* Unspecify pixel sotrage modes. */
glPixelStorei (GL_UNPACK_ROW_LENGTH_EXT, 0);
glPixelStorei (GL_UNPACK_SKIP_PIXELS_EXT, 0);
glPixelStorei (GL_UNPACK_SKIP_ROWS_EXT, 0);
/* Unbind from the texturing target. */
glBindTexture (target, 0);
/* The buffer's been copied to the texture. It can now be
released. */
buffer->flags |= CanRelease;
}
static void
EnsureTexture (EglBuffer *buffer)
{
/* If a texture has already been created, return. */
if (buffer->flags & IsTextureGenerated)
return;
/* If the buffer does not need textures, return. */
if (buffer->u.type == SinglePixelBuffer)
return;
/* Generate the name for the texture. */
glGenTextures (1, &buffer->texture);
/* Update all texture data. */
UpdateTexture (buffer);
/* Mark the texture as generated. */
buffer->flags |= IsTextureGenerated;
}
static void
UpdateBuffer (RenderBuffer buffer, pixman_region32_t *damage,
DrawParams *params)
{
EglBuffer *egl_buffer;
egl_buffer = buffer.pointer;
/* Single pixel buffers don't need updates. */
if (egl_buffer->u.type == SinglePixelBuffer)
return;
if (!(egl_buffer->flags & IsTextureGenerated))
/* No texture has been generated, so just create one and maybe
upload the contents. */
EnsureTexture (egl_buffer);
else if (!damage || params->flags & TransformSet)
/* Upload all the contents to the buffer's texture if the buffer
type requires manual updates. Buffers backed by EGLImages do
not appear to need updates, since updates to the EGLImage are
automatically reflected in the texture.
However, someone on #dri says calling
glEGLImageTargetTexture2DOES is still required and not doing so
may cause certain drivers to stop working in the future. So
still do it for buffers backed by EGLImages. */
UpdateTexture (egl_buffer);
else if (pixman_region32_not_empty (damage))
{
switch (egl_buffer->u.type)
{
case ShmBuffer:
/* Update the shared memory buffer incrementally, taking
into account the damaged area and transform. */
UpdateShmBufferIncrementally (egl_buffer, damage, params);
break;
case DmaBufBuffer:
/* See comment in !damage branch. */
UpdateTexture (egl_buffer);
break;
default:
break;
}
}
}
static void
UpdateBufferForDamage (RenderBuffer buffer, pixman_region32_t *damage,
DrawParams *params)
{
UpdateBuffer (buffer, damage, params);
}
static Bool
CanReleaseNow (RenderBuffer buffer)
{
EglBuffer *egl_buffer;
Bool rc;
egl_buffer = buffer.pointer;
/* Return if texture contents were copied. */
rc = (egl_buffer->flags & CanRelease) != 0;
/* Clear that flag now. */
egl_buffer->flags &= ~CanRelease;
return rc;
}
static Bool
IsBufferOpaque (RenderBuffer buffer)
{
EglBuffer *egl_buffer;
egl_buffer = buffer.pointer;
/* Return whether or not the buffer has no alpha channel. */
return !(egl_buffer->flags & HasAlpha);
}
static BufferFuncs egl_buffer_funcs =
{
.get_drm_formats = GetDrmFormats,
.get_render_devices = GetRenderDevices,
.get_shm_formats = GetShmFormats,
.buffer_from_dma_buf = BufferFromDmaBuf,
.buffer_from_dma_buf_async = BufferFromDmaBufAsync,
.buffer_from_shm = BufferFromShm,
.validate_shm_params = ValidateShmParams,
.buffer_from_single_pixel = BufferFromSinglePixel,
.free_shm_buffer = FreeShmBuffer,
.free_dmabuf_buffer = FreeDmabufBuffer,
.free_single_pixel_buffer = FreeSinglePixelBuffer,
.update_buffer_for_damage = UpdateBufferForDamage,
.can_release_now = CanReleaseNow,
.is_buffer_opaque = IsBufferOpaque,
.init_buffer_funcs = InitBufferFuncs,
};
void
InitEgl (void)
{
RegisterStaticRenderer ("egl", &egl_render_funcs,
&egl_buffer_funcs);
}