/* 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); }