From 970b60268f6fb173ef2efa9b65d8eecb09ce9cdd Mon Sep 17 00:00:00 2001 From: oldosfan Date: Fri, 23 Sep 2022 08:28:48 +0000 Subject: [PATCH] Check in new files for EGL support * egl.c: * picture_renderer.c: * renderer.c: * shaders.awk: * shaders.txt: New files. Move old XRender code to picture_renderer.c. --- egl.c | 1982 ++++++++++++++++++++++++++++++++++++++++++++ picture_renderer.c | 1141 +++++++++++++++++++++++++ renderer.c | 335 ++++++++ shaders.awk | 53 ++ shaders.txt | 80 ++ 5 files changed, 3591 insertions(+) create mode 100644 egl.c create mode 100644 picture_renderer.c create mode 100644 renderer.c create mode 100644 shaders.awk create mode 100644 shaders.txt 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); +} +//==