diff --git a/egl.c b/egl.c
new file mode 100644
index 0000000..157f176
--- /dev/null
+++ b/egl.c
@@ -0,0 +1,1982 @@
+/* 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 . */
+
+#include
+#include
+#include
+#include
+
+#include
+
+#include "compositor.h"
+#include "shaders.h"
+
+#include
+#include
+
+#include
+#include
+
+/* 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 _FormatInfo FormatInfo;
+
+typedef struct _CompositeProgram CompositeProgram;
+
+enum _EglBufferType
+ {
+ DmaBufBuffer,
+ ShmBuffer,
+ };
+
+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 _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 = 2,
+ };
+
+struct _EglBuffer
+{
+ /* Some flags. */
+ int flags;
+
+ /* The texture name of any generated texture. */
+ GLuint texture;
+
+ /* 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;
+ } 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,
+ };
+
+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;
+};
+
+/* 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;
+
+/* 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;
+
+/* 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)
+ {
+ n = 0;
+
+ /* 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");
+}
+
+static void
+EglInitGlFuncs (void)
+{
+ LoadProcGl (EGLImageTargetTexture2D, "OES", "GL_OES_EGL_image");
+}
+
+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;
+ *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 alpha, red, green, and blue. */
+ egl_config_attribs[0] = EGL_BUFFER_SIZE;
+ egl_config_attribs[1] = 32;
+ 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] = 8;
+
+ /* 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");
+
+ /* 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);
+}
+
+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);
+
+ /* 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)
+{
+ 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);
+
+ /* 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
+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 void
+ApplyTransform (RenderBuffer buffer, double divisor)
+{
+ /* TODO... */
+}
+
+static CompositeProgram *
+FindProgram (EglBuffer *buffer)
+{
+ switch (buffer->u.type)
+ {
+ 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;
+ }
+
+ /* This is not supposed to happen. */
+ abort ();
+}
+
+static void
+Composite (RenderBuffer buffer, RenderTarget target,
+ Operation op, int src_x, int src_y, int x, int y,
+ int width, int height)
+{
+ 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;
+
+ /* Assert that a texture was generated, since UpdateBuffer should be
+ called before the buffer is ever used. */
+ XLAssert (egl_buffer->flags & IsTextureGenerated);
+
+ /* Find the program to use for compositing. */
+ program = FindProgram (egl_buffer);
+
+ /* Get the texturing target. */
+ tex_target = GetTextureTarget (egl_buffer);
+
+ /* 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);
+
+ glActiveTexture (GL_TEXTURE0);
+ glBindTexture (tex_target, egl_buffer->texture);
+ glTexParameteri (tex_target, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+ glUseProgram (program->program);
+
+ glUniform1i (program->texture, 0);
+ 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);
+
+ glBindTexture (tex_target, 0);
+}
+
+static void
+ResetTransform (RenderBuffer buffer)
+{
+ /* TODO... */
+}
+
+static void
+FinishRender (RenderTarget target)
+{
+ EglTarget *egl_target;
+
+ egl_target = target.pointer;
+
+ /* This should also do glFinish. */
+ eglSwapBuffers (egl_display, egl_target->surface);
+}
+
+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 - 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 RenderFuncs egl_render_funcs =
+ {
+ .init_render_funcs = InitRenderFuncs,
+ .target_from_window = TargetFromWindow,
+ .target_from_pixmap = TargetFromPixmap,
+ .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,
+ .apply_transform = ApplyTransform,
+ .composite = Composite,
+ .reset_transform = ResetTransform,
+ .finish_render = FinishRender,
+ .target_age = TargetAge,
+ .flags = 0,
+ };
+
+static DrmFormat *
+GetDrmFormats (int *num_formats)
+{
+ *num_formats = n_drm_formats;
+ return drm_formats;
+}
+
+static dev_t
+GetRenderDevice (Bool *error)
+{
+ *error = !drm_device_available;
+ 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;
+ 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;
+
+ /* 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;
+
+ /* 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 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);
+}
+
+/* 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);
+ }
+
+ /* Bind the target to nothing. */
+ glBindTexture (target, 0);
+}
+
+static void
+UpdateShmBufferIncrementally (EglBuffer *buffer, pixman_region32_t *damage)
+{
+ GLenum target;
+ pixman_box32_t *boxes;
+ 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)
+ {
+ if (boxes[i].x1 >= buffer->width
+ || boxes[i].y1 >= buffer->height)
+ /* This box isn't contained in the buffer. */
+ continue;
+
+ /* These computations are correct, since box->x2/box->y2 are
+ actually 1 pixel outside the last pixel in the box. */
+ width = MIN (boxes[i].x2, buffer->width) - boxes[i].x1;
+ height = MIN (boxes[i].y2, buffer->height) - boxes[i].y1;
+
+ if (width <= 0 || height <= 0)
+ /* The box is effectively empty because it straddles one of
+ the corners 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 boxes[i].x1 pixels of each row. */
+ glPixelStorei (GL_UNPACK_SKIP_PIXELS_EXT, boxes[i].x1);
+
+ /* And boxes[i].y1 rows. */
+ glPixelStorei (GL_UNPACK_SKIP_ROWS_EXT, boxes[i].y1);
+
+ /* Copy the image into the sub-texture. */
+ glTexSubImage2D (target, 0, boxes[i].x1, boxes[i].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);
+}
+
+static void
+EnsureTexture (EglBuffer *buffer)
+{
+ /* If a texture has already been created, return. */
+ if (buffer->flags & IsTextureGenerated)
+ 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)
+{
+ EglBuffer *egl_buffer;
+
+ egl_buffer = buffer.pointer;
+
+ 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)
+ {
+ /* Upload all the contents to the buffer's texture if the buffer
+ type requires manual updates. Buffers backed by EGLImages do
+ not need updates, since updates to the EGLImage are
+ automatically reflected in the texture. */
+
+ if (egl_buffer->u.type == ShmBuffer)
+ UpdateTexture (egl_buffer);
+ }
+ else
+ {
+ switch (egl_buffer->u.type)
+ {
+ case ShmBuffer:
+ /* Update the shared memory buffer incrementally, taking
+ into account the damaged area. */
+ UpdateShmBufferIncrementally (egl_buffer, damage);
+ break;
+
+ default:
+ /* These buffers need no updates. */
+ break;
+ }
+ }
+}
+
+static void
+UpdateBufferForDamage (RenderBuffer buffer, pixman_region32_t *damage)
+{
+ /* TODO: handle scaling. */
+ UpdateBuffer (buffer, damage);
+}
+
+static BufferFuncs egl_buffer_funcs =
+ {
+ .get_drm_formats = GetDrmFormats,
+ .get_render_device = GetRenderDevice,
+ .get_shm_formats = GetShmFormats,
+ .buffer_from_dma_buf = BufferFromDmaBuf,
+ .buffer_from_dma_buf_async = BufferFromDmaBufAsync,
+ .buffer_from_shm = BufferFromShm,
+ .validate_shm_params = ValidateShmParams,
+ .free_shm_buffer = FreeShmBuffer,
+ .free_dmabuf_buffer = FreeDmabufBuffer,
+ .update_buffer_for_damage = UpdateBufferForDamage,
+ .init_buffer_funcs = InitBufferFuncs,
+ };
+
+void
+InitEgl (void)
+{
+ RegisterStaticRenderer ("egl", &egl_render_funcs,
+ &egl_buffer_funcs);
+}
diff --git a/picture_renderer.c b/picture_renderer.c
new file mode 100644
index 0000000..b9274e8
--- /dev/null
+++ b/picture_renderer.c
@@ -0,0 +1,1141 @@
+/* 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 . */
+
+#include
+#include
+#include
+#include
+
+#include
+#include
+
+#include "compositor.h"
+
+#include
+
+#include
+#include
+
+typedef struct _DrmFormatInfo DrmFormatInfo;
+typedef struct _DmaBufRecord DmaBufRecord;
+
+struct _DrmFormatInfo
+{
+ /* The DRM format code. */
+ uint32_t format_code;
+
+ /* The X Windows depth. */
+ int depth;
+
+ /* The X Windows green, red, blue, and alpha masks. */
+ int red, green, blue, alpha;
+
+ /* The number of bits per pixel. */
+ int bits_per_pixel;
+
+ /* PictFormat associated with this format, or NULL if none were
+ found. */
+ XRenderPictFormat *format;
+
+ /* List of supported screen modifiers. */
+ uint64_t *supported_modifiers;
+
+ /* Number of supported screen modifiers. */
+ int n_supported_modifiers;
+};
+
+struct _DmaBufRecord
+{
+ /* The XID of the pixmap. */
+ Pixmap pixmap;
+
+ /* The success callback. */
+ DmaBufSuccessFunc success_func;
+
+ /* The failure callback. */
+ DmaBufFailureFunc failure_func;
+
+ /* The callback data. */
+ void *data;
+
+ /* The picture format that will be used. */
+ XRenderPictFormat *format;
+
+ /* The next and last pending buffers in this list. */
+ DmaBufRecord *next, *last;
+};
+
+/* The identity transform. */
+
+static XTransform identity_transform;
+
+/* The default SHM formats. */
+
+static ShmFormat default_formats[] =
+ {
+ { WL_SHM_FORMAT_ARGB8888 },
+ { WL_SHM_FORMAT_XRGB8888 },
+ };
+
+/* List of all supported DRM formats. */
+static DrmFormatInfo all_formats[] =
+ {
+ {
+ .format_code = DRM_FORMAT_ARGB8888,
+ .depth = 32,
+ .red = 0xff0000,
+ .green = 0xff00,
+ .blue = 0xff,
+ .alpha = 0xff000000,
+ .bits_per_pixel = 32,
+ },
+ {
+ .format_code = DRM_FORMAT_XRGB8888,
+ .depth = 24,
+ .red = 0xff0000,
+ .green = 0xff00,
+ .blue = 0xff,
+ .alpha = 0,
+ .bits_per_pixel = 32,
+ },
+ {
+ .format_code = DRM_FORMAT_XBGR8888,
+ .depth = 24,
+ .blue = 0xff0000,
+ .green = 0xff00,
+ .red = 0xff,
+ .alpha = 0,
+ .bits_per_pixel = 32,
+ },
+ {
+ .format_code = DRM_FORMAT_ABGR8888,
+ .depth = 32,
+ .blue = 0xff0000,
+ .green = 0xff00,
+ .red = 0xff,
+ .alpha = 0xff000000,
+ .bits_per_pixel = 32,
+ },
+ {
+ .format_code = DRM_FORMAT_BGRA8888,
+ .depth = 32,
+ .blue = 0xff000000,
+ .green = 0xff0000,
+ .red = 0xff00,
+ .alpha = 0xff,
+ .bits_per_pixel = 32,
+ },
+ };
+
+/* DRM formats reported to the caller. */
+static DrmFormat *drm_formats;
+
+/* Number of formats available. */
+static int n_drm_formats;
+
+/* List of buffers that are still pending asynchronous creation. */
+static DmaBufRecord pending_success;
+
+/* The id of the next round trip event. */
+static uint64_t next_roundtrip_id;
+
+/* A window used to receive round trip events. */
+static Window round_trip_window;
+
+/* The opcode of the DRI3 extension. */
+static int dri3_opcode;
+
+/* XRender and DRI3-based renderer. A RenderTarget is just a
+ Picture. */
+
+static Visual *
+PickVisual (int *depth)
+{
+ int n_visuals;
+ XVisualInfo vinfo, *visuals;
+ Visual *selection;
+
+ vinfo.screen = DefaultScreen (compositor.display);
+ vinfo.class = TrueColor;
+ vinfo.depth = 32;
+
+ visuals = XGetVisualInfo (compositor.display, (VisualScreenMask
+ | VisualClassMask
+ | VisualDepthMask),
+ &vinfo, &n_visuals);
+
+ if (n_visuals)
+ {
+ selection = visuals[0].visual;
+ *depth = visuals[0].depth;
+ XFree (visuals);
+
+ return selection;
+ }
+
+ return NULL;
+}
+
+static Bool
+InitRenderFuncs (void)
+{
+ /* Set up the default visual. */
+ compositor.visual = PickVisual (&compositor.n_planes);
+
+ /* Return success if the visual was found. */
+ return compositor.visual != NULL;
+}
+
+static RenderTarget
+TargetFromDrawable (Drawable drawable)
+{
+ 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);
+
+ return (RenderTarget) XRenderCreatePicture (compositor.display,
+ drawable,
+ compositor.argb_format,
+ 0, &picture_attrs);
+}
+
+static RenderTarget
+TargetFromPixmap (Pixmap pixmap)
+{
+ return TargetFromDrawable (pixmap);
+}
+
+static RenderTarget
+TargetFromWindow (Window window)
+{
+ return TargetFromDrawable (window);
+}
+
+static Picture
+PictureFromTarget (RenderTarget target)
+{
+ return target.xid;
+}
+
+static void
+FreePictureFromTarget (Picture picture)
+{
+ /* There is no need to free these pictures. */
+}
+
+static void
+DestroyRenderTarget (RenderTarget target)
+{
+ XRenderFreePicture (compositor.display, target.xid);
+}
+
+static void
+FillBoxesWithTransparency (RenderTarget target, pixman_box32_t *boxes,
+ int nboxes, int min_x, int min_y)
+{
+ XRectangle *rects;
+ static XRenderColor color;
+ int i;
+
+ if (nboxes < 256)
+ rects = alloca (sizeof *rects * nboxes);
+ else
+ rects = XLMalloc (sizeof *rects * nboxes);
+
+ for (i = 0; i < nboxes; ++i)
+ {
+ rects[i].x = BoxStartX (boxes[i]) - min_x;
+ rects[i].y = BoxStartY (boxes[i]) - min_y;
+ rects[i].width = BoxWidth (boxes[i]);
+ rects[i].height = BoxHeight (boxes[i]);
+ }
+
+ XRenderFillRectangles (compositor.display, PictOpClear,
+ target.xid, &color, rects, nboxes);
+
+ if (nboxes >= 256)
+ XLFree (rects);
+}
+
+static void
+ClearRectangle (RenderTarget target, int x, int y, int width, int height)
+{
+ static XRenderColor color;
+
+ XRenderFillRectangle (compositor.display, PictOpClear,
+ target.xid, &color, x, y, width, height);
+}
+
+static void
+ApplyTransform (RenderBuffer buffer, double divisor)
+{
+ XTransform transform;
+
+ memset (&transform, 0, sizeof transform);
+
+ transform.matrix[0][0] = XDoubleToFixed (divisor);
+ transform.matrix[1][1] = XDoubleToFixed (divisor);
+ transform.matrix[2][2] = XDoubleToFixed (1);
+
+ XRenderSetPictureTransform (compositor.display, buffer.xid,
+ &transform);
+}
+
+static int
+ConvertOperation (Operation op)
+{
+ switch (op)
+ {
+ case OperationOver:
+ return PictOpOver;
+
+ case OperationSource:
+ return PictOpSrc;
+ }
+
+ abort ();
+}
+
+static void
+Composite (RenderBuffer buffer, RenderTarget target,
+ Operation op, int src_x, int src_y, int x, int y,
+ int width, int height)
+{
+ XRenderComposite (compositor.display, ConvertOperation (op),
+ buffer.xid, None, target.xid,
+ /* src-x, src-y, mask-x, mask-y. */
+ src_x, src_y, 0, 0,
+ /* dst-x, dst-y, width, height. */
+ x, y, width, height);
+}
+
+static void
+ResetTransform (RenderBuffer buffer)
+{
+ XRenderSetPictureTransform (compositor.display, buffer.xid,
+ &identity_transform);
+}
+
+static int
+TargetAge (RenderTarget target)
+{
+ return 0;
+}
+
+static RenderFuncs picture_render_funcs =
+ {
+ .init_render_funcs = InitRenderFuncs,
+ .target_from_window = TargetFromWindow,
+ .target_from_pixmap = TargetFromPixmap,
+ .picture_from_target = PictureFromTarget,
+ .free_picture_from_target = FreePictureFromTarget,
+ .destroy_render_target = DestroyRenderTarget,
+ .fill_boxes_with_transparency = FillBoxesWithTransparency,
+ .clear_rectangle = ClearRectangle,
+ .apply_transform = ApplyTransform,
+ .composite = Composite,
+ .reset_transform = ResetTransform,
+ .target_age = TargetAge,
+ .flags = NeverAges,
+ };
+
+static DrmFormatInfo *
+FindFormatMatching (XRenderPictFormat *format)
+{
+ unsigned long alpha, red, green, blue;
+ int i;
+
+ if (format->type != PictTypeDirect)
+ /* No DRM formats are colormapped. */
+ return NULL;
+
+ alpha = format->direct.alphaMask << format->direct.alpha;
+ red = format->direct.redMask << format->direct.red;
+ green = format->direct.greenMask << format->direct.green;
+ blue = format->direct.blueMask << format->direct.blue;
+
+ for (i = 0; i < ArrayElements (all_formats); ++i)
+ {
+ if (all_formats[i].depth == format->depth
+ && all_formats[i].red == red
+ && all_formats[i].green == green
+ && all_formats[i].blue == blue
+ && all_formats[i].alpha == alpha)
+ return &all_formats[i];
+ }
+
+ return NULL;
+}
+
+static Bool
+FindSupportedFormats (void)
+{
+ int count;
+ XRenderPictFormat *format;
+ DrmFormatInfo *info;
+ Bool supported;
+
+ count = 0;
+ supported = False;
+
+ do
+ {
+ format = XRenderFindFormat (compositor.display, 0,
+ NULL, count);
+ count++;
+
+ if (!format)
+ break;
+
+ info = FindFormatMatching (format);
+
+ if (info && !info->format)
+ info->format = format;
+
+ if (info)
+ supported = True;
+ }
+ while (format);
+
+ return supported;
+}
+
+static void
+FindSupportedModifiers (int *pair_count_return)
+{
+ Window root_window;
+ xcb_dri3_get_supported_modifiers_cookie_t *cookies;
+ xcb_dri3_get_supported_modifiers_reply_t *reply;
+ int i, length, pair_count;
+ uint64_t *mods;
+
+ cookies = alloca (sizeof *cookies * ArrayElements (all_formats));
+ root_window = DefaultRootWindow (compositor.display);
+ pair_count = 0;
+
+ for (i = 0; i < ArrayElements (all_formats); ++i)
+ {
+ if (all_formats[i].format)
+ {
+ cookies[i]
+ = xcb_dri3_get_supported_modifiers (compositor.conn,
+ root_window, all_formats[i].depth,
+ all_formats[i].bits_per_pixel);
+
+ /* pair_count is the number of format-modifier pairs that
+ will be returned. First, add one for each implicit
+ modifier. */
+ pair_count += 1;
+ }
+ }
+
+ for (i = 0; i < ArrayElements (all_formats); ++i)
+ {
+ if (!all_formats[i].format)
+ continue;
+
+ reply = xcb_dri3_get_supported_modifiers_reply (compositor.conn,
+ cookies[i], NULL);
+
+ if (!reply)
+ continue;
+
+ mods
+ = xcb_dri3_get_supported_modifiers_screen_modifiers (reply);
+ length
+ = xcb_dri3_get_supported_modifiers_screen_modifiers_length (reply);
+
+ all_formats[i].supported_modifiers = XLMalloc (sizeof *mods * length);
+ all_formats[i].n_supported_modifiers = length;
+
+ /* Then, add length for each explicit modifier. */
+ pair_count += length;
+
+ memcpy (all_formats[i].supported_modifiers, mods,
+ sizeof *mods * length);
+ free (reply);
+ }
+
+ *pair_count_return = pair_count;
+}
+
+static void
+InitDrmFormats (void)
+{
+ int pair_count, i, j, n;
+
+ /* First, look up which formats are supported. */
+ if (!FindSupportedFormats ())
+ return;
+
+ /* Then, look up modifiers. */
+ FindSupportedModifiers (&pair_count);
+
+ /* Allocate the amount of memory we need to store the DRM format
+ list. */
+ drm_formats = XLCalloc (pair_count, sizeof *drm_formats);
+ n = 0;
+
+ /* Populate the format list. */
+ for (i = 0; i < ArrayElements (all_formats); ++i)
+ {
+ if (!all_formats[i].format)
+ continue;
+
+ /* Check n < pair_count. */
+ XLAssert (n < pair_count);
+
+ /* Add the implicit modifier. */
+ drm_formats[n].drm_format = all_formats[i].format_code;
+ drm_formats[n].drm_modifier = DRM_FORMAT_MOD_INVALID;
+ n++;
+
+ /* Now add every supported explicit modifier. */
+ for (j = 0; j < all_formats[i].n_supported_modifiers; ++i)
+ {
+ /* Check n < pair_count. */
+ XLAssert (n < pair_count);
+
+ /* Add the implicit modifier. */
+ drm_formats[n].drm_format = all_formats[i].format_code;
+ drm_formats[n].drm_modifier
+ = all_formats[i].supported_modifiers[j];
+ n++;
+ }
+ }
+
+ /* Set the number of supported formats to the pair count. */
+ n_drm_formats = pair_count;
+
+ /* Return. */
+ return;
+}
+
+static DrmFormat *
+GetDrmFormats (int *num_formats)
+{
+ *num_formats = n_drm_formats;
+ return drm_formats;
+}
+
+static dev_t
+GetRenderDevice (Bool *error)
+{
+ xcb_dri3_open_cookie_t cookie;
+ xcb_dri3_open_reply_t *reply;
+ int *fds, fd;
+ struct stat dev_stat;
+
+ /* TODO: if this ever calls exec, set FD_CLOEXEC, and implement
+ multiple providers. */
+ cookie = xcb_dri3_open (compositor.conn,
+ DefaultRootWindow (compositor.display),
+ None);
+ reply = xcb_dri3_open_reply (compositor.conn, cookie, NULL);
+
+ if (!reply)
+ {
+ *error = True;
+ return (dev_t) 0;
+ }
+
+ fds = xcb_dri3_open_reply_fds (compositor.conn, reply);
+
+ if (!fds)
+ {
+ free (reply);
+ *error = True;
+
+ return (dev_t) 0;
+ }
+
+ fd = fds[0];
+
+ if (fstat (fd, &dev_stat) != 0)
+ {
+ close (fd);
+ free (reply);
+ *error = True;
+
+ return (dev_t) 0;
+ }
+
+ if (!dev_stat.st_rdev)
+ {
+ close (fd);
+ free (reply);
+ *error = True;
+
+ return (dev_t) 0;
+ }
+
+ close (fd);
+ free (reply);
+
+ return dev_stat.st_rdev;
+}
+
+static ShmFormat *
+GetShmFormats (int *num_formats)
+{
+ /* Return the two mandatory shm formats. */
+ *num_formats = ArrayElements (default_formats);
+ return default_formats;
+}
+
+static int
+DepthForDmabufFormat (uint32_t drm_format, int *bits_per_pixel)
+{
+ int i;
+
+ for (i = 0; i < ArrayElements (all_formats); ++i)
+ {
+ if (all_formats[i].format_code == drm_format
+ && all_formats[i].format)
+ {
+ *bits_per_pixel = all_formats[i].bits_per_pixel;
+
+ return all_formats[i].depth;
+ }
+ }
+
+ return -1;
+}
+
+static XRenderPictFormat *
+PictFormatForDmabufFormat (uint32_t drm_format)
+{
+ int i;
+
+ for (i = 0; i < ArrayElements (all_formats); ++i)
+ {
+ if (all_formats[i].format_code == drm_format
+ && all_formats[i].format)
+ return all_formats[i].format;
+ }
+
+ /* This shouldn't happen, since the format was already verified in
+ Create. */
+ abort ();
+}
+
+static void
+CloseFileDescriptors (DmaBufAttributes *attributes)
+{
+ int i;
+
+ for (i = 0; i < attributes->n_planes; ++i)
+ close (attributes->fds[i]);
+}
+
+static RenderBuffer
+BufferFromDmaBuf (DmaBufAttributes *attributes, Bool *error)
+{
+ int depth, bpp;
+ Pixmap pixmap;
+ Picture picture;
+ xcb_void_cookie_t check_cookie;
+ xcb_generic_error_t *xerror;
+ XRenderPictFormat *format;
+ XRenderPictureAttributes picture_attrs;
+
+ /* Find the depth and bpp corresponding to the format. */
+ depth = DepthForDmabufFormat (attributes->drm_format, &bpp);
+
+ /* Flags are not supported. */
+ if (attributes->flags || depth == -1)
+ goto error;
+
+ /* Create the pixmap. */
+ pixmap = xcb_generate_id (compositor.conn);
+ check_cookie
+ = xcb_dri3_pixmap_from_buffers (compositor.conn, pixmap,
+ DefaultRootWindow (compositor.display),
+ attributes->n_planes,
+ attributes->width,
+ attributes->height,
+ attributes->offsets[0],
+ attributes->strides[0],
+ attributes->offsets[1],
+ attributes->strides[1],
+ attributes->offsets[2],
+ attributes->strides[2],
+ attributes->offsets[3],
+ attributes->strides[3],
+ depth, bpp,
+ attributes->modifier,
+ attributes->fds);
+ xerror = xcb_request_check (compositor.conn, check_cookie);
+
+ /* A platform specific error occured creating this buffer. Signal
+ failure. */
+ if (xerror)
+ {
+ free (xerror);
+ goto error;
+ }
+
+ /* Otherwise, create the picture and free the pixmap. */
+ format = PictFormatForDmabufFormat (attributes->drm_format);
+ XLAssert (format != NULL);
+
+ picture = XRenderCreatePicture (compositor.display, pixmap,
+ format, 0, &picture_attrs);
+ XFreePixmap (compositor.display, pixmap);
+
+ return (RenderBuffer) picture;
+
+ error:
+ CloseFileDescriptors (attributes);
+ *error = True;
+ return (RenderBuffer) (XID) None;
+}
+
+static void
+ForceRoundTrip (void)
+{
+ uint64_t id;
+ XEvent event;
+
+ /* Send an event with a monotonically increasing identifier to
+ ourselves.
+
+ Once the last event is received, create the actual buffers for
+ each buffer resource for which error handlers have not run. */
+
+ id = next_roundtrip_id++;
+
+ memset (&event, 0, sizeof event);
+
+ event.xclient.type = ClientMessage;
+ event.xclient.window = round_trip_window;
+ event.xclient.message_type = _XL_DMA_BUF_CREATED;
+ event.xclient.format = 32;
+
+ event.xclient.data.l[0] = id >> 31 >> 1;
+ event.xclient.data.l[1] = id & 0xffffffff;
+
+ XSendEvent (compositor.display, round_trip_window,
+ False, NoEventMask, &event);
+}
+
+static void
+FinishDmaBufRecord (DmaBufRecord *pending, Bool success)
+{
+ Picture picture;
+ XRenderPictureAttributes picture_attrs;
+
+ if (success)
+ {
+ /* This is just to pacify GCC. */
+ memset (&picture_attrs, 0, sizeof picture_attrs);
+
+ picture = XRenderCreatePicture (compositor.display,
+ pending->pixmap,
+ pending->format, 0,
+ &picture_attrs);
+ XFreePixmap (compositor.display, pending->pixmap);
+
+ /* Call the creation success function with the new picture. */
+ pending->success_func ((RenderBuffer) picture,
+ pending->data);
+ }
+ else
+ /* Call the failure function with the data. */
+ pending->failure_func (pending->data);
+
+ /* Unlink the creation record. */
+ pending->last->next = pending->next;
+ pending->next->last = pending->last;
+
+ XLFree (pending);
+}
+
+static void
+FinishBufferCreation (void)
+{
+ DmaBufRecord *next, *last;
+
+ /* It is now known that all records in pending_success have been
+ created. Create pictures and call the success function for
+ each. */
+ next = pending_success.next;
+ while (next != &pending_success)
+ {
+ last = next;
+ next = next->next;
+
+ FinishDmaBufRecord (last, True);
+ }
+}
+
+/* N.B. that the caller is supposed to keep callback_data around until
+ one of success_func or failure_func is called. */
+
+static void
+BufferFromDmaBufAsync (DmaBufAttributes *attributes,
+ DmaBufSuccessFunc success_func,
+ DmaBufFailureFunc failure_func,
+ void *callback_data)
+{
+ DmaBufRecord *record;
+ int depth, bpp;
+ Pixmap pixmap;
+
+ /* Find the depth and bpp corresponding to the format. */
+ depth = DepthForDmabufFormat (attributes->drm_format, &bpp);
+
+ /* Flags are not supported. */
+ if (attributes->flags || depth == -1)
+ goto error;
+
+ /* Create the pixmap. */
+ pixmap = xcb_generate_id (compositor.conn);
+ xcb_dri3_pixmap_from_buffers (compositor.conn, pixmap,
+ DefaultRootWindow (compositor.display),
+ attributes->n_planes, attributes->width,
+ attributes->height,
+ attributes->offsets[0],
+ attributes->strides[0],
+ attributes->offsets[1],
+ attributes->strides[1],
+ attributes->offsets[2],
+ attributes->strides[2],
+ attributes->offsets[3],
+ attributes->strides[3],
+ depth, bpp,
+ attributes->modifier, attributes->fds);
+
+ /* Then, link the resulting pixmap and callbacks onto the list of
+ pending buffers. Right now, we do not know if the creation will
+ be rejected by the X server, so we first arrange to catch all
+ errors from DRI3PixmapFromBuffers, and send the create event the
+ next time we know that a roundtrip has happened without any
+ errors being raised. */
+
+ record = XLMalloc (sizeof *record);
+ record->success_func = success_func;
+ record->failure_func = failure_func;
+ record->data = callback_data;
+ record->format
+ = PictFormatForDmabufFormat (attributes->drm_format);
+ record->pixmap = pixmap;
+ XLAssert (record->format != NULL);
+
+ record->next = pending_success.next;
+ record->last = &pending_success;
+ pending_success.next->last = record;
+ pending_success.next = record;
+
+ ForceRoundTrip ();
+
+ return;
+
+ error:
+ /* Just call the failure func to signal failure this early on. */
+ failure_func (callback_data);
+
+ /* Then, close the file descriptors. */
+ CloseFileDescriptors (attributes);
+}
+
+static int
+DepthForFormat (uint32_t format)
+{
+ switch (format)
+ {
+ case WL_SHM_FORMAT_ARGB8888:
+ return 32;
+
+ case WL_SHM_FORMAT_XRGB8888:
+ return 24;
+
+ default:
+ return 0;
+ }
+}
+
+static XRenderPictFormat *
+PictFormatForFormat (uint32_t format)
+{
+ switch (format)
+ {
+ case WL_SHM_FORMAT_ARGB8888:
+ return compositor.argb_format;
+
+ case WL_SHM_FORMAT_XRGB8888:
+ return compositor.xrgb_format;
+
+ default:
+ return 0;
+ }
+}
+
+static RenderBuffer
+BufferFromShm (SharedMemoryAttributes *attributes, Bool *error)
+{
+ XRenderPictureAttributes picture_attrs;
+ xcb_shm_seg_t seg;
+ Pixmap pixmap;
+ Picture picture;
+ int fd, depth, format;
+
+ depth = DepthForFormat (attributes->format);
+ format = attributes->format;
+
+ /* Duplicate the fd, since XCB closes file descriptors after sending
+ them. */
+ fd = fcntl (attributes->fd, F_DUPFD_CLOEXEC, 0);
+
+ if (fd < 0)
+ {
+ *error = True;
+ return (RenderBuffer) (XID) None;
+ }
+
+ /* Now, allocate the XIDs for the shm segment and pixmap. */
+ seg = xcb_generate_id (compositor.conn);
+ pixmap = xcb_generate_id (compositor.conn);
+
+ /* Create the segment and attach the pixmap to it. */
+ xcb_shm_attach_fd (compositor.conn, seg, fd, false);
+ xcb_shm_create_pixmap (compositor.conn, pixmap,
+ DefaultRootWindow (compositor.display),
+ attributes->width, attributes->height,
+ depth, seg, attributes->offset);
+ xcb_shm_detach (compositor.conn, seg);
+
+ /* Create the picture for the pixmap, and free the pixmap. */
+ picture = XRenderCreatePicture (compositor.display, pixmap,
+ PictFormatForFormat (format),
+ 0, &picture_attrs);
+ XFreePixmap (compositor.display, pixmap);
+
+ /* Return the picture. */
+ return (RenderBuffer) picture;
+}
+
+static Bool
+ValidateShmParams (uint32_t format, uint32_t width, uint32_t height,
+ int32_t offset, int32_t stride, size_t pool_size)
+{
+ if (pool_size < offset || stride != width * 4
+ || offset + stride * height > pool_size
+ || offset < 0)
+ return False;
+
+ return True;
+}
+
+static void
+FreeShmBuffer (RenderBuffer buffer)
+{
+ XRenderFreePicture (compositor.display, buffer.xid);
+}
+
+static void
+FreeDmabufBuffer (RenderBuffer buffer)
+{
+ /* N.B. that the picture is the only reference to the pixmap
+ here. */
+ XRenderFreePicture (compositor.display, buffer.xid);
+}
+
+static void
+SetupMitShm (void)
+{
+ xcb_shm_query_version_reply_t *reply;
+ xcb_shm_query_version_cookie_t cookie;
+
+ /* This shouldn't be freed. */
+ const xcb_query_extension_reply_t *ext;
+
+ ext = xcb_get_extension_data (compositor.conn, &xcb_shm_id);
+
+ if (!ext || !ext->present)
+ {
+ fprintf (stderr, "The MIT-SHM extension is not supported by this X server.\n");
+ exit (1);
+ }
+
+ cookie = xcb_shm_query_version (compositor.conn);
+ reply = xcb_shm_query_version_reply (compositor.conn,
+ cookie, NULL);
+
+ if (!reply)
+ {
+ fprintf (stderr, "The MIT-SHM extension on this X server is too old.\n");
+ exit (1);
+ }
+ else if (reply->major_version < 1
+ || (reply->major_version == 1
+ && reply->minor_version < 2))
+ {
+ fprintf (stderr, "The MIT-SHM extension on this X server is too old"
+ " to support POSIX shared memory.\n");
+ exit (1);
+ }
+
+ free (reply);
+}
+
+static void
+InitBufferFuncs (void)
+{
+ xcb_dri3_query_version_cookie_t cookie;
+ xcb_dri3_query_version_reply_t *reply;
+ const xcb_query_extension_reply_t *ext;
+
+ /* Set up the MIT shared memory extension. It is required to
+ work. */
+ SetupMitShm ();
+
+ /* XRender should already have been set up; it is used for things
+ other than rendering as well. */
+
+ ext = xcb_get_extension_data (compositor.conn, &xcb_dri3_id);
+ reply = NULL;
+
+ if (ext && ext->present)
+ {
+ cookie = xcb_dri3_query_version (compositor.conn, 1, 2);
+ reply = xcb_dri3_query_version_reply (compositor.conn, cookie,
+ NULL);
+
+ if (!reply)
+ goto error;
+
+ if (reply->major_version < 1
+ || (reply->major_version == 1
+ && reply->minor_version < 2))
+ goto error;
+
+ dri3_opcode = ext->major_opcode;
+
+ /* Initialize DRM formats. */
+ InitDrmFormats ();
+ }
+ else
+ error:
+ fprintf (stderr, "Warning: the X server does not support a new enough version of"
+ " the DRI3 extension.\nHardware acceleration will not be available.\n");
+
+ if (reply)
+ free (reply);
+}
+
+static BufferFuncs picture_buffer_funcs =
+ {
+ .get_drm_formats = GetDrmFormats,
+ .get_render_device = GetRenderDevice,
+ .get_shm_formats = GetShmFormats,
+ .buffer_from_dma_buf = BufferFromDmaBuf,
+ .buffer_from_dma_buf_async = BufferFromDmaBufAsync,
+ .buffer_from_shm = BufferFromShm,
+ .validate_shm_params = ValidateShmParams,
+ .free_shm_buffer = FreeShmBuffer,
+ .free_dmabuf_buffer = FreeDmabufBuffer,
+ .init_buffer_funcs = InitBufferFuncs,
+ };
+
+Bool
+HandleErrorForPictureRenderer (XErrorEvent *error)
+{
+ DmaBufRecord *record, *next;
+
+ if (error->request_code == dri3_opcode
+ && error->minor_code == xDRI3BuffersFromPixmap)
+ {
+ /* Something chouldn't be created. Find what failed and unlink
+ it. */
+
+ next = pending_success.next;
+
+ while (next != &pending_success)
+ {
+ record = next;
+ next = next->next;
+
+ if (record->pixmap == error->resourceid)
+ {
+ /* Call record's failure callback and unlink it. */
+ FinishDmaBufRecord (record, False);
+ break;
+ }
+ }
+
+ return True;
+ }
+
+ return False;
+}
+
+Bool
+HandleOneXEventForPictureRenderer (XEvent *event)
+{
+ uint64_t id, low, high;
+
+ if (event->type == ClientMessage
+ && event->xclient.message_type == _XL_DMA_BUF_CREATED)
+ {
+ /* Values are masked against 0xffffffff, as Xlib sign-extends
+ those longs. */
+ high = event->xclient.data.l[0] & 0xffffffff;
+ low = event->xclient.data.l[1] & 0xffffffff;
+ id = low | (high << 32);
+
+ /* Ignore the message if the id is too old. */
+ if (id < next_roundtrip_id)
+ /* Otherwise, it means buffer creation was successful.
+ Complete all pending buffer creation. */
+ FinishBufferCreation ();
+
+ return True;
+ }
+
+ return False;
+}
+
+void
+InitPictureRenderer (void)
+{
+ XSetWindowAttributes attrs;
+
+ identity_transform.matrix[0][0] = 1;
+ identity_transform.matrix[1][1] = 1;
+ identity_transform.matrix[2][2] = 1;
+
+ pending_success.next = &pending_success;
+ pending_success.last = &pending_success;
+
+ /* Create an unmapped, InputOnly window, that is used to receive
+ roundtrip events. */
+ attrs.override_redirect = True;
+ round_trip_window = XCreateWindow (compositor.display,
+ DefaultRootWindow (compositor.display),
+ -1, -1, 1, 1, 0, CopyFromParent, InputOnly,
+ CopyFromParent, CWOverrideRedirect,
+ &attrs);
+
+ /* Initialize the renderer with our functions. */
+ RegisterStaticRenderer ("picture", &picture_render_funcs,
+ &picture_buffer_funcs);
+}
diff --git a/renderer.c b/renderer.c
new file mode 100644
index 0000000..de3d53b
--- /dev/null
+++ b/renderer.c
@@ -0,0 +1,335 @@
+/* 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 . */
+
+#include
+#include
+#include
+
+#include "compositor.h"
+
+/* Structure defining a registered renderer. */
+
+typedef struct _Renderer Renderer;
+
+struct _Renderer
+{
+ /* The next renderer in the chain of available renderers. */
+ Renderer *next;
+
+ /* The name of this renderer. */
+ const char *name;
+
+ /* Set of buffer manipulation functions. */
+ BufferFuncs *buffer_funcs;
+
+ /* Set of rendering manipulation functions. */
+ RenderFuncs *render_funcs;
+};
+
+/* Chain of all renderers. */
+static Renderer *renderers;
+
+/* The selected buffer functions. */
+static BufferFuncs buffer_funcs;
+
+/* The selected render functions. */
+static RenderFuncs render_funcs;
+
+/* Flags of the selected renderer. */
+int renderer_flags;
+
+static Renderer *
+AllocateRenderer (void)
+{
+ static Renderer renderers[2];
+ static int used;
+
+ if (used < ArrayElements (renderers))
+ return &renderers[used++];
+
+ /* When adding a new renderer, make sure to update the number of
+ renderers in the above array. */
+ abort ();
+}
+
+RenderTarget
+RenderTargetFromWindow (Window window)
+{
+ return render_funcs.target_from_window (window);
+}
+
+RenderTarget
+RenderTargetFromPixmap (Pixmap pixmap)
+{
+ return render_funcs.target_from_pixmap (pixmap);
+}
+
+void
+RenderNoteTargetSize (RenderTarget target, int width, int height)
+{
+ if (!render_funcs.note_target_size)
+ /* This function can be NULL. */
+ return;
+
+ render_funcs.note_target_size (target, width, height);
+}
+
+Picture
+RenderPictureFromTarget (RenderTarget target)
+{
+ return render_funcs.picture_from_target (target);
+}
+
+void
+RenderFreePictureFromTarget (Picture picture)
+{
+ render_funcs.free_picture_from_target (picture);
+}
+
+void
+RenderDestroyRenderTarget (RenderTarget target)
+{
+ render_funcs.destroy_render_target (target);
+}
+
+void
+RenderStartRender (RenderTarget target)
+{
+ if (render_funcs.start_render)
+ render_funcs.start_render (target);
+}
+
+void
+RenderFillBoxesWithTransparency (RenderTarget target, pixman_box32_t *boxes,
+ int nboxes, int min_x, int min_y)
+{
+ render_funcs.fill_boxes_with_transparency (target, boxes,
+ nboxes, min_x, min_y);
+}
+
+void
+RenderClearRectangle (RenderTarget target, int x, int y, int width, int height)
+{
+ render_funcs.clear_rectangle (target, x, y, width, height);
+}
+
+void
+RenderApplyTransform (RenderBuffer buffer, double divisor)
+{
+ render_funcs.apply_transform (buffer, divisor);
+}
+
+void
+RenderComposite (RenderBuffer source, RenderTarget target, Operation op,
+ int src_x, int src_y, int x, int y, int width, int height)
+{
+ render_funcs.composite (source, target, op, src_x, src_y, x, y,
+ width, height);
+}
+
+void
+RenderResetTransform (RenderBuffer buffer)
+{
+ render_funcs.reset_transform (buffer);
+}
+
+void
+RenderFinishRender (RenderTarget target)
+{
+ if (render_funcs.finish_render)
+ render_funcs.finish_render (target);
+}
+
+int
+RenderTargetAge (RenderTarget target)
+{
+ return render_funcs.target_age (target);
+}
+
+DrmFormat *
+RenderGetDrmFormats (int *n_formats)
+{
+ return buffer_funcs.get_drm_formats (n_formats);
+}
+
+dev_t
+RenderGetRenderDevice (Bool *error)
+{
+ return buffer_funcs.get_render_device (error);
+}
+
+ShmFormat *
+RenderGetShmFormats (int *n_formats)
+{
+ return buffer_funcs.get_shm_formats (n_formats);
+}
+
+RenderBuffer
+RenderBufferFromDmaBuf (DmaBufAttributes *attributes, Bool *error)
+{
+ return buffer_funcs.buffer_from_dma_buf (attributes, error);
+}
+
+void
+RenderBufferFromDmaBufAsync (DmaBufAttributes *attributes,
+ DmaBufSuccessFunc success_func,
+ DmaBufFailureFunc failure_func,
+ void *callback_data)
+{
+ return buffer_funcs.buffer_from_dma_buf_async (attributes,
+ success_func,
+ failure_func,
+ callback_data);
+}
+
+RenderBuffer
+RenderBufferFromShm (SharedMemoryAttributes *attributes, Bool *error)
+{
+ return buffer_funcs.buffer_from_shm (attributes, error);
+}
+
+Bool
+RenderValidateShmParams (uint32_t format, uint32_t width, uint32_t height,
+ int32_t offset, int32_t stride, size_t pool_size)
+{
+ return buffer_funcs.validate_shm_params (format, width, height,
+ offset, stride, pool_size);
+}
+
+void
+RenderFreeShmBuffer (RenderBuffer buffer)
+{
+ return buffer_funcs.free_shm_buffer (buffer);
+}
+
+void
+RenderFreeDmabufBuffer (RenderBuffer buffer)
+{
+ return buffer_funcs.free_dmabuf_buffer (buffer);
+}
+
+void
+RenderUpdateBufferForDamage (RenderBuffer buffer, pixman_region32_t *damage)
+{
+ if (!buffer_funcs.update_buffer_for_damage)
+ return;
+
+ buffer_funcs.update_buffer_for_damage (buffer, damage);
+}
+
+void
+RegisterStaticRenderer (const char *name,
+ RenderFuncs *render_funcs,
+ BufferFuncs *buffer_funcs)
+{
+ Renderer *renderer;
+
+ renderer = AllocateRenderer ();
+ renderer->next = renderers;
+ renderer->buffer_funcs = buffer_funcs;
+ renderer->render_funcs = render_funcs;
+ renderer->name = name;
+
+ renderers = renderer;
+}
+
+static Bool
+InstallRenderer (Renderer *renderer)
+{
+ buffer_funcs = *renderer->buffer_funcs;
+ render_funcs = *renderer->render_funcs;
+
+ /* Now, initialize the renderer by calling its init functions. */
+
+ if (!render_funcs.init_render_funcs ())
+ /* If this returns false, then the renderer cannot be used. */
+ return False;
+
+ buffer_funcs.init_buffer_funcs ();
+
+ /* Finally, set the flags. The idea is that init_render_funcs
+ and/or init_buffer_funcs might change this, so we set them from
+ renderer->render_funcs. */
+ renderer_flags = renderer->render_funcs->flags;
+
+ return True;
+}
+
+static void
+PickRenderer (void)
+{
+ const char *selected;
+ Renderer *renderer;
+
+ /* Install and initialize the first renderer in the list. */
+ XLAssert (renderers != NULL);
+
+ selected = getenv ("RENDERER");
+
+ if (selected)
+ {
+ /* If selected is "help", print each renderer and exit. */
+ if (!strcmp (selected, "help"))
+ {
+ fprintf (stderr, "The following rendering backends can be used:\n");
+
+ for (renderer = renderers; renderer; renderer = renderer->next)
+ fprintf (stderr, " %s\n", renderer->name);
+
+ exit (0);
+ }
+
+ /* Otherwise, look for a renderer matching the given name and
+ install it. */
+ for (renderer = renderers; renderer; renderer = renderer->next)
+ {
+ if (!strcmp (renderer->name, selected))
+ {
+ if (!InstallRenderer (renderer))
+ {
+ fprintf (stderr, "Failed to initialize renderer %s, "
+ "defaulting to %s instead.\n", selected,
+ renderers->name);
+ goto fall_back;
+ }
+
+ return;
+ }
+ }
+
+ /* Fall back to the default renderer. */
+ fprintf (stderr, "Defaulting to renderer %s, as %s was not found\n",
+ renderers->name, selected);
+ fall_back:
+ }
+
+ if (!InstallRenderer (renderers))
+ abort ();
+}
+
+void
+InitRenderers (void)
+{
+#ifdef HaveEglSupport
+ InitEgl ();
+#endif
+ InitPictureRenderer ();
+
+ PickRenderer ();
+}
diff --git a/shaders.awk b/shaders.awk
new file mode 100644
index 0000000..8c7074b
--- /dev/null
+++ b/shaders.awk
@@ -0,0 +1,53 @@
+# Wayland compositor running on top of an X serer.
+
+# 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 .
+
+/^\/\/==/ {
+ if (in_program)
+ {
+ in_program = 0;
+ print ";"
+ }
+ else
+ {
+ match ($0, /^\/\/== ([[:alnum:] ]+)/, array)
+ in_program = ignore_line = 1;
+
+ # Start a new shader; first, escape the name by replacing white
+ # space with underscores.
+ gsub (/[[:space:]]/, "_", array[1])
+
+ # Next, make everything lowercase.
+ array[1] = tolower (array[1]);
+
+ printf "static const char *%s =\n", array[1]
+ }
+}
+
+{
+ if (ignore_line)
+ ignore_line = 0;
+ else if (in_program)
+ {
+ # Escape characters that can occur in regular GLSL programs but
+ # must be escaped in C strings.
+ string = $0
+ gsub (/[\\"]/, "\\\\&", string)
+ printf " \"%s\\n\"\n", string
+ }
+}
diff --git a/shaders.txt b/shaders.txt
new file mode 100644
index 0000000..452dd3c
--- /dev/null
+++ b/shaders.txt
@@ -0,0 +1,80 @@
+// -*- glsl -*-
+
+// File containing GLSL shaders used to generate shaders.h.
+
+// At the start of each shader, write // followed by two equals signs,
+// followed by a space, the name of the shader and a newline.
+// Following that, write the shader code.
+
+// To terminate a shader, write // followed by another two equals
+// signs, this time without a trailing name or whitespace.
+
+//== Clear Rectangle Vertex Shader
+attribute vec2 pos;
+
+void
+main (void)
+{
+ gl_Position = vec4 (pos.x, pos.y, 1.0, 1.0);
+}
+//==
+
+//== Clear Rectangle Fragment Shader
+void
+main (void)
+{
+ gl_FragColor = vec4 (0.0, 0.0, 0.0, 1.0);
+}
+//==
+
+//== Composite Rectangle Vertex Shader
+attribute vec2 pos;
+attribute vec2 texcoord;
+varying vec2 v_texcoord;
+
+void
+main (void)
+{
+ gl_Position = vec4 (pos.x, pos.y, 1.0, 1.0);
+ v_texcoord = texcoord;
+}
+//==
+
+//== Composite Rectangle Fragment Shader RGBA
+precision mediump float;
+uniform sampler2D texture;
+varying vec2 v_texcoord;
+
+void
+main (void)
+{
+ gl_FragColor = texture2D (texture, v_texcoord);
+}
+//==
+
+//== Composite Rectangle Fragment Shader RGBX
+precision mediump float;
+uniform sampler2D texture;
+varying vec2 v_texcoord;
+
+void
+main (void)
+{
+ gl_FragColor = vec4 (texture2D (texture, v_texcoord).rgb,
+ 1.0);
+}
+//==
+
+//== Composite Rectangle Fragment Shader External
+#extension GL_OES_EGL_image_external : require
+
+precision mediump float;
+uniform samplerExternalOES texture;
+varying vec2 v_texcoord;
+
+void
+main (void)
+{
+ gl_FragColor = texture2D (texture, v_texcoord);
+}
+//==