From 55e5165e2fe0fff014c4d154bcb8972b2abac586 Mon Sep 17 00:00:00 2001 From: gold Date: Thu, 1 Oct 2026 03:16:55 +0800 Subject: [PATCH] [Feature] (gbm): MobileGL's own GBM, so the compositor's buffers are the display host's frames kwin never creates an EGL window surface. Its undefined libgbm symbols are seventeen entry points whose whole purpose is to hand out gbm_bo fds, and those fds are what Anland's display producer sends to the daemon on the phone - so the question "whose pixels are on the screen" is answered by "who allocated the bo", and today that is Mesa. The client can be loaded, initialized and rendering through glvnd with no window surface ever created, which is exactly what was measured. This is a complete GBM: every public entry point of the vendored Mesa header, with the semantics a compositor relies on - fds the caller owns, real mmap with refcounting, a swapchain that refuses to hand out a busy buffer, import by fd, and teardown that leaves no descriptor or mapping behind. The buffers come from the display host's frames; where there is no host, the device allocates from the dma-buf heap, falling back to an uncached heap and then to memfd, so the library stands on its own. The vendored gbm.h is Mesa's, verbatim: the entry points are the ABI, not our idea of it. --- 3rdparty/gbm/include/gbm.h | 538 ++++++++++++++++ CMakeLists.txt | 59 ++ MobileGL/MG_Gbm/GbmApi.cpp | 663 +++++++++++++++++++ MobileGL/MG_Gbm/GbmDevice.cpp | 1128 +++++++++++++++++++++++++++++++++ MobileGL/MG_Gbm/GbmInternal.h | 358 +++++++++++ 5 files changed, 2746 insertions(+) create mode 100644 3rdparty/gbm/include/gbm.h create mode 100644 MobileGL/MG_Gbm/GbmApi.cpp create mode 100644 MobileGL/MG_Gbm/GbmDevice.cpp create mode 100644 MobileGL/MG_Gbm/GbmInternal.h diff --git a/3rdparty/gbm/include/gbm.h b/3rdparty/gbm/include/gbm.h new file mode 100644 index 00000000..9c680f65 --- /dev/null +++ b/3rdparty/gbm/include/gbm.h @@ -0,0 +1,538 @@ +/* + * Copyright © 2011 Intel Corporation + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + * + * Authors: + * Benjamin Franzke + */ + +#ifndef _GBM_H_ +#define _GBM_H_ + +#define __GBM__ 1 + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + + +/** + * \file gbm.h + * \brief Generic Buffer Manager + */ + +struct gbm_device; +struct gbm_bo; +struct gbm_surface; + +/** + * \mainpage The Generic Buffer Manager + * + * This module provides an abstraction that the caller can use to request a + * buffer from the underlying memory management system for the platform. + * + * This allows the creation of portable code whilst still allowing access to + * the underlying memory manager. + */ + +/** + * Abstraction representing the handle to a buffer allocated by the + * manager + */ +union gbm_bo_handle { + void *ptr; + int32_t s32; + uint32_t u32; + int64_t s64; + uint64_t u64; +}; + +/** Format of the allocated buffer */ +enum gbm_bo_format { + /** RGB with 8 bits per channel in a 32 bit value */ + GBM_BO_FORMAT_XRGB8888, + /** ARGB with 8 bits per channel in a 32 bit value */ + GBM_BO_FORMAT_ARGB8888 +}; + + +/** + * The FourCC format codes are taken from the drm_fourcc.h definition, and + * re-namespaced. New GBM formats must not be added, unless they are + * identical ports from drm_fourcc. + */ +#define __gbm_fourcc_code(a,b,c,d) ((uint32_t)(a) | ((uint32_t)(b) << 8) | \ + ((uint32_t)(c) << 16) | ((uint32_t)(d) << 24)) + +#define GBM_FORMAT_BIG_ENDIAN (1<<31) /* format is big endian instead of little endian */ + +/* color index */ +#define GBM_FORMAT_C8 __gbm_fourcc_code('C', '8', ' ', ' ') /* [7:0] C */ + +/* 8 bpp Red */ +#define GBM_FORMAT_R8 __gbm_fourcc_code('R', '8', ' ', ' ') /* [7:0] R */ + +/* 16 bpp Red */ +#define GBM_FORMAT_R16 __gbm_fourcc_code('R', '1', '6', ' ') /* [15:0] R little endian */ + +/* 16 bpp RG */ +#define GBM_FORMAT_GR88 __gbm_fourcc_code('G', 'R', '8', '8') /* [15:0] G:R 8:8 little endian */ + +/* 32 bpp RG */ +#define GBM_FORMAT_RG1616 __gbm_fourcc_code('R', 'G', '3', '2') /* [31:0] R:G 16:16 little endian */ +#define GBM_FORMAT_GR1616 __gbm_fourcc_code('G', 'R', '3', '2') /* [31:0] G:R 16:16 little endian */ + +/* 8 bpp RGB */ +#define GBM_FORMAT_RGB332 __gbm_fourcc_code('R', 'G', 'B', '8') /* [7:0] R:G:B 3:3:2 */ +#define GBM_FORMAT_BGR233 __gbm_fourcc_code('B', 'G', 'R', '8') /* [7:0] B:G:R 2:3:3 */ + +/* 16 bpp RGB */ +#define GBM_FORMAT_XRGB4444 __gbm_fourcc_code('X', 'R', '1', '2') /* [15:0] x:R:G:B 4:4:4:4 little endian */ +#define GBM_FORMAT_XBGR4444 __gbm_fourcc_code('X', 'B', '1', '2') /* [15:0] x:B:G:R 4:4:4:4 little endian */ +#define GBM_FORMAT_RGBX4444 __gbm_fourcc_code('R', 'X', '1', '2') /* [15:0] R:G:B:x 4:4:4:4 little endian */ +#define GBM_FORMAT_BGRX4444 __gbm_fourcc_code('B', 'X', '1', '2') /* [15:0] B:G:R:x 4:4:4:4 little endian */ + +#define GBM_FORMAT_ARGB4444 __gbm_fourcc_code('A', 'R', '1', '2') /* [15:0] A:R:G:B 4:4:4:4 little endian */ +#define GBM_FORMAT_ABGR4444 __gbm_fourcc_code('A', 'B', '1', '2') /* [15:0] A:B:G:R 4:4:4:4 little endian */ +#define GBM_FORMAT_RGBA4444 __gbm_fourcc_code('R', 'A', '1', '2') /* [15:0] R:G:B:A 4:4:4:4 little endian */ +#define GBM_FORMAT_BGRA4444 __gbm_fourcc_code('B', 'A', '1', '2') /* [15:0] B:G:R:A 4:4:4:4 little endian */ + +#define GBM_FORMAT_XRGB1555 __gbm_fourcc_code('X', 'R', '1', '5') /* [15:0] x:R:G:B 1:5:5:5 little endian */ +#define GBM_FORMAT_XBGR1555 __gbm_fourcc_code('X', 'B', '1', '5') /* [15:0] x:B:G:R 1:5:5:5 little endian */ +#define GBM_FORMAT_RGBX5551 __gbm_fourcc_code('R', 'X', '1', '5') /* [15:0] R:G:B:x 5:5:5:1 little endian */ +#define GBM_FORMAT_BGRX5551 __gbm_fourcc_code('B', 'X', '1', '5') /* [15:0] B:G:R:x 5:5:5:1 little endian */ + +#define GBM_FORMAT_ARGB1555 __gbm_fourcc_code('A', 'R', '1', '5') /* [15:0] A:R:G:B 1:5:5:5 little endian */ +#define GBM_FORMAT_ABGR1555 __gbm_fourcc_code('A', 'B', '1', '5') /* [15:0] A:B:G:R 1:5:5:5 little endian */ +#define GBM_FORMAT_RGBA5551 __gbm_fourcc_code('R', 'A', '1', '5') /* [15:0] R:G:B:A 5:5:5:1 little endian */ +#define GBM_FORMAT_BGRA5551 __gbm_fourcc_code('B', 'A', '1', '5') /* [15:0] B:G:R:A 5:5:5:1 little endian */ + +#define GBM_FORMAT_RGB565 __gbm_fourcc_code('R', 'G', '1', '6') /* [15:0] R:G:B 5:6:5 little endian */ +#define GBM_FORMAT_BGR565 __gbm_fourcc_code('B', 'G', '1', '6') /* [15:0] B:G:R 5:6:5 little endian */ + +/* 24 bpp RGB */ +#define GBM_FORMAT_RGB888 __gbm_fourcc_code('R', 'G', '2', '4') /* [23:0] R:G:B little endian */ +#define GBM_FORMAT_BGR888 __gbm_fourcc_code('B', 'G', '2', '4') /* [23:0] B:G:R little endian */ + +/* 32 bpp RGB */ +#define GBM_FORMAT_XRGB8888 __gbm_fourcc_code('X', 'R', '2', '4') /* [31:0] x:R:G:B 8:8:8:8 little endian */ +#define GBM_FORMAT_XBGR8888 __gbm_fourcc_code('X', 'B', '2', '4') /* [31:0] x:B:G:R 8:8:8:8 little endian */ +#define GBM_FORMAT_RGBX8888 __gbm_fourcc_code('R', 'X', '2', '4') /* [31:0] R:G:B:x 8:8:8:8 little endian */ +#define GBM_FORMAT_BGRX8888 __gbm_fourcc_code('B', 'X', '2', '4') /* [31:0] B:G:R:x 8:8:8:8 little endian */ + +#define GBM_FORMAT_ARGB8888 __gbm_fourcc_code('A', 'R', '2', '4') /* [31:0] A:R:G:B 8:8:8:8 little endian */ +#define GBM_FORMAT_ABGR8888 __gbm_fourcc_code('A', 'B', '2', '4') /* [31:0] A:B:G:R 8:8:8:8 little endian */ +#define GBM_FORMAT_RGBA8888 __gbm_fourcc_code('R', 'A', '2', '4') /* [31:0] R:G:B:A 8:8:8:8 little endian */ +#define GBM_FORMAT_BGRA8888 __gbm_fourcc_code('B', 'A', '2', '4') /* [31:0] B:G:R:A 8:8:8:8 little endian */ + +#define GBM_FORMAT_XRGB2101010 __gbm_fourcc_code('X', 'R', '3', '0') /* [31:0] x:R:G:B 2:10:10:10 little endian */ +#define GBM_FORMAT_XBGR2101010 __gbm_fourcc_code('X', 'B', '3', '0') /* [31:0] x:B:G:R 2:10:10:10 little endian */ +#define GBM_FORMAT_RGBX1010102 __gbm_fourcc_code('R', 'X', '3', '0') /* [31:0] R:G:B:x 10:10:10:2 little endian */ +#define GBM_FORMAT_BGRX1010102 __gbm_fourcc_code('B', 'X', '3', '0') /* [31:0] B:G:R:x 10:10:10:2 little endian */ + +#define GBM_FORMAT_ARGB2101010 __gbm_fourcc_code('A', 'R', '3', '0') /* [31:0] A:R:G:B 2:10:10:10 little endian */ +#define GBM_FORMAT_ABGR2101010 __gbm_fourcc_code('A', 'B', '3', '0') /* [31:0] A:B:G:R 2:10:10:10 little endian */ +#define GBM_FORMAT_RGBA1010102 __gbm_fourcc_code('R', 'A', '3', '0') /* [31:0] R:G:B:A 10:10:10:2 little endian */ +#define GBM_FORMAT_BGRA1010102 __gbm_fourcc_code('B', 'A', '3', '0') /* [31:0] B:G:R:A 10:10:10:2 little endian */ + +/* 64 bpp RGB */ +#define GBM_FORMAT_XBGR16161616 __gbm_fourcc_code('X', 'B', '4', '8') /* [63:0] x:B:G:R 16:16:16:16 little endian */ + +#define GBM_FORMAT_ABGR16161616 __gbm_fourcc_code('A', 'B', '4', '8') /* [63:0] A:B:G:R 16:16:16:16 little endian */ + +/* + * Floating point 64bpp RGB + * IEEE 754-2008 binary16 half-precision float + * [15:0] sign:exponent:mantissa 1:5:10 + */ +#define GBM_FORMAT_XBGR16161616F __gbm_fourcc_code('X', 'B', '4', 'H') /* [63:0] x:B:G:R 16:16:16:16 little endian */ + +#define GBM_FORMAT_ABGR16161616F __gbm_fourcc_code('A', 'B', '4', 'H') /* [63:0] A:B:G:R 16:16:16:16 little endian */ + +/* packed YCbCr */ +#define GBM_FORMAT_YUYV __gbm_fourcc_code('Y', 'U', 'Y', 'V') /* [31:0] Cr0:Y1:Cb0:Y0 8:8:8:8 little endian */ +#define GBM_FORMAT_YVYU __gbm_fourcc_code('Y', 'V', 'Y', 'U') /* [31:0] Cb0:Y1:Cr0:Y0 8:8:8:8 little endian */ +#define GBM_FORMAT_UYVY __gbm_fourcc_code('U', 'Y', 'V', 'Y') /* [31:0] Y1:Cr0:Y0:Cb0 8:8:8:8 little endian */ +#define GBM_FORMAT_VYUY __gbm_fourcc_code('V', 'Y', 'U', 'Y') /* [31:0] Y1:Cb0:Y0:Cr0 8:8:8:8 little endian */ + +#define GBM_FORMAT_AYUV __gbm_fourcc_code('A', 'Y', 'U', 'V') /* [31:0] A:Y:Cb:Cr 8:8:8:8 little endian */ + +/* + * 2 plane YCbCr + * index 0 = Y plane, [7:0] Y + * index 1 = Cr:Cb plane, [15:0] Cr:Cb little endian + * or + * index 1 = Cb:Cr plane, [15:0] Cb:Cr little endian + */ +#define GBM_FORMAT_NV12 __gbm_fourcc_code('N', 'V', '1', '2') /* 2x2 subsampled Cr:Cb plane */ +#define GBM_FORMAT_NV21 __gbm_fourcc_code('N', 'V', '2', '1') /* 2x2 subsampled Cb:Cr plane */ +#define GBM_FORMAT_NV16 __gbm_fourcc_code('N', 'V', '1', '6') /* 2x1 subsampled Cr:Cb plane */ +#define GBM_FORMAT_NV61 __gbm_fourcc_code('N', 'V', '6', '1') /* 2x1 subsampled Cb:Cr plane */ + +/* + * 3 plane YCbCr + * index 0: Y plane, [7:0] Y + * index 1: Cb plane, [7:0] Cb + * index 2: Cr plane, [7:0] Cr + * or + * index 1: Cr plane, [7:0] Cr + * index 2: Cb plane, [7:0] Cb + */ +#define GBM_FORMAT_YUV410 __gbm_fourcc_code('Y', 'U', 'V', '9') /* 4x4 subsampled Cb (1) and Cr (2) planes */ +#define GBM_FORMAT_YVU410 __gbm_fourcc_code('Y', 'V', 'U', '9') /* 4x4 subsampled Cr (1) and Cb (2) planes */ +#define GBM_FORMAT_YUV411 __gbm_fourcc_code('Y', 'U', '1', '1') /* 4x1 subsampled Cb (1) and Cr (2) planes */ +#define GBM_FORMAT_YVU411 __gbm_fourcc_code('Y', 'V', '1', '1') /* 4x1 subsampled Cr (1) and Cb (2) planes */ +#define GBM_FORMAT_YUV420 __gbm_fourcc_code('Y', 'U', '1', '2') /* 2x2 subsampled Cb (1) and Cr (2) planes */ +#define GBM_FORMAT_YVU420 __gbm_fourcc_code('Y', 'V', '1', '2') /* 2x2 subsampled Cr (1) and Cb (2) planes */ +#define GBM_FORMAT_YUV422 __gbm_fourcc_code('Y', 'U', '1', '6') /* 2x1 subsampled Cb (1) and Cr (2) planes */ +#define GBM_FORMAT_YVU422 __gbm_fourcc_code('Y', 'V', '1', '6') /* 2x1 subsampled Cr (1) and Cb (2) planes */ +#define GBM_FORMAT_YUV444 __gbm_fourcc_code('Y', 'U', '2', '4') /* non-subsampled Cb (1) and Cr (2) planes */ +#define GBM_FORMAT_YVU444 __gbm_fourcc_code('Y', 'V', '2', '4') /* non-subsampled Cr (1) and Cb (2) planes */ + +struct gbm_format_name_desc { + char name[5]; +}; + +/** + * Flags to indicate the intended use for the buffer - these are passed into + * gbm_bo_create(). The caller must set the union of all the flags that are + * appropriate + * + * \sa Use gbm_device_is_format_supported() to check if the combination of format + * and use flags are supported + */ +enum gbm_bo_flags { + /** + * Buffer is going to be presented to the screen using an API such as KMS + */ + GBM_BO_USE_SCANOUT = (1 << 0), + /** + * Buffer is going to be used as cursor + */ + GBM_BO_USE_CURSOR = (1 << 1), + /** + * Deprecated + */ + GBM_BO_USE_CURSOR_64X64 = GBM_BO_USE_CURSOR, + /** + * Buffer is to be used for rendering - for example it is going to be used + * as the storage for a color buffer + */ + GBM_BO_USE_RENDERING = (1 << 2), + /** + * Buffer can be used for gbm_bo_write. This is guaranteed to work + * with GBM_BO_USE_CURSOR, but may not work for other combinations. + */ + GBM_BO_USE_WRITE = (1 << 3), + /** + * Buffer is linear, i.e. not tiled. + */ + GBM_BO_USE_LINEAR = (1 << 4), + /** + * Buffer is protected, i.e. encrypted and not readable by CPU or any + * other non-secure / non-trusted components nor by non-trusted OpenGL, + * OpenCL, and Vulkan applications. + */ + GBM_BO_USE_PROTECTED = (1 << 5), + + /** + * The buffer will be used for front buffer rendering. On some + * platforms this may (for example) disable framebuffer compression + * to avoid problems with compression flags data being out of sync + * with pixel data. + */ + GBM_BO_USE_FRONT_RENDERING = (1 << 6), + + /** + * Allow the driver to select fixed-rate compression parameters. + */ + GBM_BO_FIXED_COMPRESSION_DEFAULT = (1 << 7), + + /** + * Fixed-rate compression: at least 1bpc, less than 2bpc + */ + GBM_BO_FIXED_COMPRESSION_1BPC = (2 << 7), + + /** + * Fixed-rate compression: at least 2bpc, less than 3bpc + */ + GBM_BO_FIXED_COMPRESSION_2BPC = (3 << 7), + + /** + * Fixed-rate compression: at least 3bpc, less than 4bpc + */ + GBM_BO_FIXED_COMPRESSION_3BPC = (4 << 7), + + /** + * Fixed-rate compression: at least 4bpc, less than 5bpc + */ + GBM_BO_FIXED_COMPRESSION_4BPC = (5 << 7), + + /** + * Fixed-rate compression: at least 5bpc, less than 6bpc + */ + GBM_BO_FIXED_COMPRESSION_5BPC = (6 << 7), + + /** + * Fixed-rate compression: at least 6bpc, less than 7bpc + */ + GBM_BO_FIXED_COMPRESSION_6BPC = (7 << 7), + + /** + * Fixed-rate compression: at least 7bpc, less than 8bpc + */ + GBM_BO_FIXED_COMPRESSION_7BPC = (8 << 7), + + /** + * Fixed-rate compression: at least 8bpc, less than 9bpc + */ + GBM_BO_FIXED_COMPRESSION_8BPC = (9 << 7), + + /** + * Fixed-rate compression: at least 9bpc, less than 10bpc + */ + GBM_BO_FIXED_COMPRESSION_9BPC = (10 << 7), + + /** + * Fixed-rate compression: at least 10bpc, less than 11bpc + */ + GBM_BO_FIXED_COMPRESSION_10BPC = (11 << 7), + + /** + * Fixed-rate compression: at least 11bpc, less than 12bpc + */ + GBM_BO_FIXED_COMPRESSION_11BPC = (12 << 7), + + /** + * Fixed-rate compression: at least 12bpc, no maximum rate + */ + GBM_BO_FIXED_COMPRESSION_12BPC = (13 << 7), + + /* next available value is (1 << 11) */ +}; + +#define GBM_BO_FIXED_COMPRESSION_MASK (((1 << 11) - 1) & ~((1 << 7) - 1)) + +int +gbm_device_get_fd(struct gbm_device *gbm); + +const char * +gbm_device_get_backend_name(struct gbm_device *gbm); + +int +gbm_device_is_format_supported(struct gbm_device *gbm, + uint32_t format, uint32_t flags); + +int +gbm_device_get_format_modifier_plane_count(struct gbm_device *gbm, + uint32_t format, + uint64_t modifier); + +void +gbm_device_destroy(struct gbm_device *gbm); + +struct gbm_device * +gbm_create_device(int fd); + +struct gbm_bo * +gbm_bo_create(struct gbm_device *gbm, + uint32_t width, uint32_t height, + uint32_t format, uint32_t flags); + +struct gbm_bo * +gbm_bo_create_with_modifiers(struct gbm_device *gbm, + uint32_t width, uint32_t height, + uint32_t format, + const uint64_t *modifiers, + const unsigned int count); + +struct gbm_bo * +gbm_bo_create_with_modifiers2(struct gbm_device *gbm, + uint32_t width, uint32_t height, + uint32_t format, + const uint64_t *modifiers, + const unsigned int count, + uint32_t flags); + +#define GBM_BO_IMPORT_WL_BUFFER 0x5501 +#define GBM_BO_IMPORT_EGL_IMAGE 0x5502 +#define GBM_BO_IMPORT_FD 0x5503 +#define GBM_BO_IMPORT_FD_MODIFIER 0x5504 + +struct gbm_import_fd_data { + int fd; + uint32_t width; + uint32_t height; + uint32_t stride; + uint32_t format; +}; + +#define GBM_MAX_PLANES 4 + +struct gbm_import_fd_modifier_data { + uint32_t width; + uint32_t height; + uint32_t format; + uint32_t num_fds; + int fds[GBM_MAX_PLANES]; + int strides[GBM_MAX_PLANES]; + int offsets[GBM_MAX_PLANES]; + uint64_t modifier; +}; + +struct gbm_bo * +gbm_bo_import(struct gbm_device *gbm, uint32_t type, + void *buffer, uint32_t flags); + +/** + * Flags to indicate the type of mapping for the buffer - these are + * passed into gbm_bo_map(). The caller must set the union of all the + * flags that are appropriate. + * + * These flags are independent of the GBM_BO_USE_* creation flags. However, + * mapping the buffer may require copying to/from a staging buffer. + * + * See also: pipe_map_flags + */ +enum gbm_bo_transfer_flags { + /** + * Buffer contents read back (or accessed directly) at transfer + * create time. + */ + GBM_BO_TRANSFER_READ = (1 << 0), + /** + * Buffer contents will be written back at unmap time + * (or modified as a result of being accessed directly). + */ + GBM_BO_TRANSFER_WRITE = (1 << 1), + /** + * Read/modify/write + */ + GBM_BO_TRANSFER_READ_WRITE = (GBM_BO_TRANSFER_READ | GBM_BO_TRANSFER_WRITE), +}; + +void * +gbm_bo_map(struct gbm_bo *bo, + uint32_t x, uint32_t y, uint32_t width, uint32_t height, + uint32_t flags, uint32_t *stride, void **map_data); + +void +gbm_bo_unmap(struct gbm_bo *bo, void *map_data); + +uint32_t +gbm_bo_get_width(struct gbm_bo *bo); + +uint32_t +gbm_bo_get_height(struct gbm_bo *bo); + +uint32_t +gbm_bo_get_stride(struct gbm_bo *bo); + +uint32_t +gbm_bo_get_stride_for_plane(struct gbm_bo *bo, int plane); + +uint32_t +gbm_bo_get_format(struct gbm_bo *bo); + +uint32_t +gbm_bo_get_bpp(struct gbm_bo *bo); + +uint32_t +gbm_bo_get_offset(struct gbm_bo *bo, int plane); + +struct gbm_device * +gbm_bo_get_device(struct gbm_bo *bo); + +union gbm_bo_handle +gbm_bo_get_handle(struct gbm_bo *bo); + +int +gbm_bo_get_fd(struct gbm_bo *bo); + +uint64_t +gbm_bo_get_modifier(struct gbm_bo *bo); + +int +gbm_bo_get_plane_count(struct gbm_bo *bo); + +union gbm_bo_handle +gbm_bo_get_handle_for_plane(struct gbm_bo *bo, int plane); + +int +gbm_bo_get_fd_for_plane(struct gbm_bo *bo, int plane); + +int +gbm_bo_write(struct gbm_bo *bo, const void *buf, size_t count); + +void +gbm_bo_set_user_data(struct gbm_bo *bo, void *data, + void (*destroy_user_data)(struct gbm_bo *, void *)); + +void * +gbm_bo_get_user_data(struct gbm_bo *bo); + +void +gbm_bo_destroy(struct gbm_bo *bo); + +struct gbm_surface * +gbm_surface_create(struct gbm_device *gbm, + uint32_t width, uint32_t height, + uint32_t format, uint32_t flags); + +struct gbm_surface * +gbm_surface_create_with_modifiers(struct gbm_device *gbm, + uint32_t width, uint32_t height, + uint32_t format, + const uint64_t *modifiers, + const unsigned int count); + +struct gbm_surface * +gbm_surface_create_with_modifiers2(struct gbm_device *gbm, + uint32_t width, uint32_t height, + uint32_t format, + const uint64_t *modifiers, + const unsigned int count, + uint32_t flags); + +struct gbm_bo * +gbm_surface_lock_front_buffer(struct gbm_surface *surface); + +void +gbm_surface_release_buffer(struct gbm_surface *surface, struct gbm_bo *bo); + +int +gbm_surface_has_free_buffers(struct gbm_surface *surface); + +void +gbm_surface_destroy(struct gbm_surface *surface); + +char * +gbm_format_get_name(uint32_t gbm_format, struct gbm_format_name_desc *desc); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/CMakeLists.txt b/CMakeLists.txt index 290c191e..222fe42b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -889,6 +889,65 @@ if(NOT ANDROID) ) endif() +# --------------------------------------------------------------------------- +# MobileGLGbm: the GBM drop-in the container's compositor loads. +# +# kwin - the Wayland compositor in the container half of the split - allocates the +# buffers it renders into and scans out through libgbm, and the dma-buf fds it gets +# back are what the Anland display producer ships to the phone. Mesa's libgbm cannot +# serve that: its buffers belong to the host GPU, and the phone would receive a copy +# through a path that was never designed to carry one. This target is the library +# that is loaded in its place, under the name a loader looks for. +# +# It is deliberately NOT part of libMobileGL.so. The compositor needs one file named +# libgbm.so that it loads instead of Mesa's, and folding this code into the GL +# library would put a second GL frontend, a second Vulkan instance and a second +# glslang in the compositor's address space to serve an API that shares none of them. +# +# The include paths below are borrowed rather than linked. Includes.h - which this +# library includes for the MobileGL types and the MGLOG macros - pulls in the GL, +# EGL, Vulkan, glslang, SPIRV-Cross and xxHash HEADERS transitively, and headers are +# all this library wants from them: linking any of those static libraries into a +# libgbm would embed a copy of each in a process that already has one. +# +# -Bsymbolic-functions, for the same reason the main target carries it: the point of +# this library is to REPLACE a libgbm that may already be loaded in the process, so +# every internal reference has to bind to this file's own definitions rather than to +# whatever else the process has under the same names. +# --------------------------------------------------------------------------- +add_library(MobileGLGbm SHARED + MobileGL/MG_Gbm/GbmDevice.cpp + MobileGL/MG_Gbm/GbmApi.cpp + # MGLOG_I/W/E expand to a call to MobileGL::MG_Util::Debug::Log, and this is the + # one translation unit that defines it. It is compiled in rather than linked from + # libMobileGL.so because this library has to load into a compositor that has no + # libMobileGL in it at all - and because a libgbm with an undefined symbol is a + # library that fails on the first diagnostic rather than at a link step somebody + # could have read. + MobileGL/MG_Util/Debug/Log.cpp +) + +# The vendored gbm.h first: this library implements THAT declaration set, and a +# system from another Mesa version on the include path ahead of it would +# change the ABI under the implementations in GbmApi.cpp. +target_include_directories(MobileGLGbm PRIVATE + ${CMAKE_SOURCE_DIR}/3rdparty/gbm/include + ${MOBILEGL_INCLUDE_DIR} + ${CMAKE_SOURCE_DIR}/3rdparty/glslang + ${CMAKE_SOURCE_DIR}/3rdparty/SPIRV-Cross + ${CMAKE_SOURCE_DIR}/3rdparty/xxHash + ${CMAKE_SOURCE_DIR}/3rdparty/Vulkan-Headers/include +) + +set_target_properties(MobileGLGbm PROPERTIES + OUTPUT_NAME gbm + POSITION_INDEPENDENT_CODE ON +) + +if (UNIX AND NOT APPLE) + target_link_options(MobileGLGbm PRIVATE "LINKER:-Bsymbolic-functions") +endif() + if (TRACY_ENABLE) target_link_libraries(${CMAKE_PROJECT_NAME} PUBLIC Tracy::TracyClient) target_link_libraries(${CMAKE_PROJECT_NAME}_s PUBLIC Tracy::TracyClient) diff --git a/MobileGL/MG_Gbm/GbmApi.cpp b/MobileGL/MG_Gbm/GbmApi.cpp new file mode 100644 index 00000000..a1f83c14 --- /dev/null +++ b/MobileGL/MG_Gbm/GbmApi.cpp @@ -0,0 +1,663 @@ +// MobileGL - MobileGL/MG_Gbm/GbmApi.cpp +// SPDX-License-Identifier: LGPL-3.0-only +// +// The extern "C" surface: every entry point gbm.h declares, and nothing else. +// +// This file is a translation layer, and it is kept that way on purpose. Each entry +// point does three things - check the pointers it was handed, take the device's one +// mutex for the whole call, and hand the work to GbmDevice.cpp - so the concurrency +// rule ("one mutex per device, taken once at the API boundary, never inside the +// allocation code") is visible in one place instead of being spread over the +// backends. A missing lock is a bug that only shows up in the compositor, so the +// boilerplate is deliberately identical in every function. +// +// Two conventions are observable from outside and are stated here because they are +// the ones a caller can get wrong: +// +// * gbm_bo_get_fd and gbm_bo_get_fd_for_plane return a DUP the caller owns. The +// caller closes what it was given; the buffer's own descriptor is closed once, +// when the buffer is destroyed, and never by a caller's close. +// * Nothing here throws and nothing here aborts. A failure is reported the way +// GBM reports failures: null or -1 with errno set, so that the strerror(errno) +// a caller prints describes the failure that actually happened. + +#include "GbmInternal.h" + +#include + +using namespace MobileGL; +using namespace MobileGL::MG_Gbm; + +namespace +{ + // The one mutex that guards a record, reached through the device that owns it. + // Null means the pointer was not one this library handed out, and every entry + // point turns that into its own failure answer rather than dereferencing it. + // + // A buffer or surface with no owner cannot exist while the library is used + // correctly - the device destroys everything it still holds before it goes away + // - so null here is a defence against a stale pointer rather than a state the + // code produces. + std::mutex* MutexOf(struct gbm_device* device) noexcept + { + return device != nullptr ? &device->Impl.Mutex : nullptr; + } + + std::mutex* MutexOf(struct gbm_bo* bo) noexcept + { + return bo != nullptr ? MutexOf(bo->Impl.Owner) : nullptr; + } + + std::mutex* MutexOf(struct gbm_surface* surface) noexcept + { + return surface != nullptr ? MutexOf(surface->Impl.Owner) : nullptr; + } +} // namespace + +// --------------------------------------------------------------------------- +// Device +// --------------------------------------------------------------------------- + +extern "C" struct gbm_device* gbm_create_device(int fd) +{ + // No lock to take: the device being built is not reachable by any other thread + // until this call returns it. + return GbmDeviceCreate(fd); +} + +extern "C" void gbm_device_destroy(struct gbm_device* gbm) +{ + std::mutex* mutex = MutexOf(gbm); + if (mutex == nullptr) + { + return; + } + // The lock is taken and deliberately never released: the mutex lives inside the + // record this call deletes, and unlocking a destroyed mutex is worse than + // leaving a dead one locked. Any other thread touching this device after its + // owner declared it dead is already outside what GBM can be asked to survive. + mutex->lock(); + GbmDeviceDestroy(*gbm); +} + +extern "C" int gbm_device_get_fd(struct gbm_device* gbm) +{ + std::mutex* mutex = MutexOf(gbm); + if (mutex == nullptr) + { + errno = EINVAL; + return -1; + } + std::lock_guard guard(*mutex); + // The device's own dup of the fd it was created with, or -1 when it was created + // without one and no render node could be opened. That is not an error state: + // the self-allocated backend never talks to the node, so a device can be fully + // usable and still have no descriptor to report. + return gbm->Impl.DeviceFd; +} + +extern "C" const char* gbm_device_get_backend_name(struct gbm_device* gbm) +{ + std::mutex* mutex = MutexOf(gbm); + if (mutex == nullptr) + { + errno = EINVAL; + return nullptr; + } + std::lock_guard guard(*mutex); + // The name says which memory this device hands out ("mobilegl-dma-heap" or + // "mobilegl-memfd"), because the callers that read it - a compositor logging its + // backend, a peer test recording what it got - are asking exactly that. The + // string lives in the device and outlives every call that can read it. + return gbm->Impl.BackendName.c_str(); +} + +extern "C" int gbm_device_is_format_supported(struct gbm_device* gbm, uint32_t format, uint32_t flags) +{ + std::mutex* mutex = MutexOf(gbm); + if (mutex == nullptr) + { + errno = EINVAL; + return 0; + } + std::lock_guard guard(*mutex); + // Every buffer this device produces is linear, mappable and shareable, so the + // usage flags do not narrow the answer to any of the usual combinations - + // including GBM_BO_USE_SCANOUT, which a linear dma-buf from the system heap + // satisfies. The one request the backend cannot honour is protected memory. + if ((flags & GBM_BO_USE_PROTECTED) != 0) + { + return 0; + } + return GbmFormatIsSupported(format) ? 1 : 0; +} + +extern "C" int gbm_device_get_format_modifier_plane_count(struct gbm_device* gbm, uint32_t format, uint64_t modifier) +{ + std::mutex* mutex = MutexOf(gbm); + if (mutex == nullptr) + { + errno = EINVAL; + return 0; + } + std::lock_guard guard(*mutex); + // Only linear memory exists here, so any other modifier names a buffer this + // device will never produce and the answer is zero planes. INVALID is answered + // as well, because it means "no modifier was declared", which is the same + // layout question asked without an answer. + if (modifier != DrmFormatModLinear && modifier != DrmFormatModInvalid) + { + return 0; + } + GbmPlaneLayout planes[GbmMaxPlanes] = {}; + Int planeCount = 0; + SizeT totalBytes = 0; + if (!GbmDescribeFormat(format, 1, 1, planes, planeCount, totalBytes)) + { + return 0; + } + return planeCount; +} + +// --------------------------------------------------------------------------- +// Buffer objects +// --------------------------------------------------------------------------- + +extern "C" struct gbm_bo* gbm_bo_create(struct gbm_device* gbm, uint32_t width, uint32_t height, uint32_t format, + uint32_t flags) +{ + std::mutex* mutex = MutexOf(gbm); + if (mutex == nullptr) + { + errno = EINVAL; + return nullptr; + } + std::lock_guard guard(*mutex); + // No modifier list: the buffer is linear and gbm_bo_get_modifier says so. + return GbmBufferCreate(*gbm, width, height, format, flags, nullptr, 0); +} + +extern "C" struct gbm_bo* gbm_bo_create_with_modifiers(struct gbm_device* gbm, uint32_t width, uint32_t height, + uint32_t format, const uint64_t* modifiers, + const unsigned int count) +{ + // The modifier-less variant is the one with no flags parameter, so it is the + // flags-0 case of the other, and saying that here keeps one implementation. + return gbm_bo_create_with_modifiers2(gbm, width, height, format, modifiers, count, 0); +} + +extern "C" struct gbm_bo* gbm_bo_create_with_modifiers2(struct gbm_device* gbm, uint32_t width, uint32_t height, + uint32_t format, const uint64_t* modifiers, + const unsigned int count, uint32_t flags) +{ + std::mutex* mutex = MutexOf(gbm); + if (mutex == nullptr) + { + errno = EINVAL; + return nullptr; + } + std::lock_guard guard(*mutex); + return GbmBufferCreate(*gbm, width, height, format, flags, modifiers, (Uint32)count); +} + +extern "C" struct gbm_bo* gbm_bo_import(struct gbm_device* gbm, uint32_t type, void* buffer, uint32_t flags) +{ + std::mutex* mutex = MutexOf(gbm); + if (mutex == nullptr) + { + errno = EINVAL; + return nullptr; + } + std::lock_guard guard(*mutex); + return GbmBufferImport(*gbm, type, buffer, flags); +} + +extern "C" void* gbm_bo_map(struct gbm_bo* bo, uint32_t x, uint32_t y, uint32_t width, uint32_t height, + uint32_t flags, uint32_t* stride, void** map_data) +{ + std::mutex* mutex = MutexOf(bo); + if (mutex == nullptr) + { + errno = EINVAL; + return nullptr; + } + // The rectangle's size is not used to size the mapping: the whole buffer is + // mapped once, at its own length, and the rectangle is applied as an offset into + // it. That is what lets two overlapping maps share one address, and it is why a + // buffer's mapping stays valid for a caller that only ever asks for a corner. + (void)width; + (void)height; + (void)flags; + std::lock_guard guard(*mutex); + Uint32 mapStride = 0; + void* address = GbmBufferMap(*bo, x, y, mapStride); + if (address == nullptr) + { + return nullptr; + } + if (stride != nullptr) + { + *stride = mapStride; + } + if (map_data != nullptr) + { + // The same address the function returns. Callers pass back either one to + // gbm_bo_unmap, and both have to name this mapping. + *map_data = address; + } + return address; +} + +extern "C" void gbm_bo_unmap(struct gbm_bo* bo, void* map_data) +{ + std::mutex* mutex = MutexOf(bo); + if (mutex == nullptr) + { + return; + } + std::lock_guard guard(*mutex); + GbmBufferUnmap(*bo, map_data); +} + +extern "C" uint32_t gbm_bo_get_width(struct gbm_bo* bo) +{ + std::mutex* mutex = MutexOf(bo); + if (mutex == nullptr) + { + errno = EINVAL; + return 0; + } + std::lock_guard guard(*mutex); + return bo->Impl.Width; +} + +extern "C" uint32_t gbm_bo_get_height(struct gbm_bo* bo) +{ + std::mutex* mutex = MutexOf(bo); + if (mutex == nullptr) + { + errno = EINVAL; + return 0; + } + std::lock_guard guard(*mutex); + return bo->Impl.Height; +} + +extern "C" uint32_t gbm_bo_get_stride(struct gbm_bo* bo) +{ + std::mutex* mutex = MutexOf(bo); + if (mutex == nullptr) + { + errno = EINVAL; + return 0; + } + std::lock_guard guard(*mutex); + return bo->Impl.Strides[0]; +} + +extern "C" uint32_t gbm_bo_get_stride_for_plane(struct gbm_bo* bo, int plane) +{ + std::mutex* mutex = MutexOf(bo); + if (mutex == nullptr) + { + errno = EINVAL; + return 0; + } + std::lock_guard guard(*mutex); + // A plane this buffer does not have is answered with zero, which is the only + // value a caller can tell apart from a real stride. + if (plane < 0 || plane >= bo->Impl.PlaneCount) + { + errno = EINVAL; + return 0; + } + return bo->Impl.Strides[plane]; +} + +extern "C" uint32_t gbm_bo_get_format(struct gbm_bo* bo) +{ + std::mutex* mutex = MutexOf(bo); + if (mutex == nullptr) + { + errno = EINVAL; + return 0; + } + std::lock_guard guard(*mutex); + return bo->Impl.Format; +} + +extern "C" uint32_t gbm_bo_get_bpp(struct gbm_bo* bo) +{ + std::mutex* mutex = MutexOf(bo); + if (mutex == nullptr) + { + errno = EINVAL; + return 0; + } + std::lock_guard guard(*mutex); + // Bits per pixel: a 32 bit format answers 32, which is what a caller computing a + // row length in bits or a row length in bytes from the same number expects. + return bo->Impl.BytesPerPixel * 8; +} + +extern "C" uint32_t gbm_bo_get_offset(struct gbm_bo* bo, int plane) +{ + std::mutex* mutex = MutexOf(bo); + if (mutex == nullptr) + { + errno = EINVAL; + return 0; + } + std::lock_guard guard(*mutex); + if (plane < 0 || plane >= bo->Impl.PlaneCount) + { + errno = EINVAL; + return 0; + } + return bo->Impl.Offsets[plane]; +} + +extern "C" struct gbm_device* gbm_bo_get_device(struct gbm_bo* bo) +{ + std::mutex* mutex = MutexOf(bo); + if (mutex == nullptr) + { + errno = EINVAL; + return nullptr; + } + return bo->Impl.Owner; +} + +extern "C" union gbm_bo_handle gbm_bo_get_handle(struct gbm_bo* bo) +{ + union gbm_bo_handle handle; + handle.ptr = nullptr; + std::mutex* mutex = MutexOf(bo); + if (mutex == nullptr) + { + errno = EINVAL; + handle.s32 = -1; + return handle; + } + std::lock_guard guard(*mutex); + // The kernel-level name of a dma-buf in this process is its file descriptor. A + // GEM handle would be a name that exists only relative to a DRM node, and this + // library deliberately does not require one, so the fd is the only answer here + // that is true for every buffer it hands out. Callers that need a name for a + // device should ask for the fd and pass that. + handle.s32 = bo->Impl.Fds[0]; + return handle; +} + +extern "C" int gbm_bo_get_fd(struct gbm_bo* bo) +{ + std::mutex* mutex = MutexOf(bo); + if (mutex == nullptr) + { + errno = EINVAL; + return -1; + } + std::lock_guard guard(*mutex); + // A dup, close-on-exec, owned by the caller. Closing it cannot affect the buffer + // or any other caller's copy. + return GbmBufferDupFd(*bo, 0); +} + +extern "C" uint64_t gbm_bo_get_modifier(struct gbm_bo* bo) +{ + std::mutex* mutex = MutexOf(bo); + if (mutex == nullptr) + { + errno = EINVAL; + return DrmFormatModInvalid; + } + std::lock_guard guard(*mutex); + return bo->Impl.Modifier; +} + +extern "C" int gbm_bo_get_plane_count(struct gbm_bo* bo) +{ + std::mutex* mutex = MutexOf(bo); + if (mutex == nullptr) + { + errno = EINVAL; + return 0; + } + std::lock_guard guard(*mutex); + return bo->Impl.PlaneCount; +} + +extern "C" union gbm_bo_handle gbm_bo_get_handle_for_plane(struct gbm_bo* bo, int plane) +{ + union gbm_bo_handle handle; + handle.ptr = nullptr; + std::mutex* mutex = MutexOf(bo); + if (mutex == nullptr) + { + errno = EINVAL; + handle.s32 = -1; + return handle; + } + std::lock_guard guard(*mutex); + if (plane < 0 || plane >= bo->Impl.PlaneCount) + { + errno = EINVAL; + handle.s32 = -1; + return handle; + } + // Same reasoning as gbm_bo_get_handle: the fd is the buffer's kernel name. Every + // plane of a self-allocated buffer lives in the one dma-buf, so all of them + // answer with that descriptor; the plane's position inside it is + // gbm_bo_get_offset. + const Int fd = bo->Impl.Fds[plane] >= 0 ? bo->Impl.Fds[plane] : bo->Impl.Fds[0]; + handle.s32 = fd; + return handle; +} + +extern "C" int gbm_bo_get_fd_for_plane(struct gbm_bo* bo, int plane) +{ + std::mutex* mutex = MutexOf(bo); + if (mutex == nullptr) + { + errno = EINVAL; + return -1; + } + std::lock_guard guard(*mutex); + return GbmBufferDupFd(*bo, plane); +} + +extern "C" int gbm_bo_write(struct gbm_bo* bo, const void* buf, size_t count) +{ + std::mutex* mutex = MutexOf(bo); + if (mutex == nullptr || buf == nullptr) + { + errno = EINVAL; + return -1; + } + std::lock_guard guard(*mutex); + if (count == 0) + { + return 0; + } + if (count > bo->Impl.AllocationSize) + { + // Refused rather than truncated: a caller that writes 4 MiB into a 1 MiB + // buffer and is told "0 bytes written, success" would read back memory it + // never wrote and call the result correct. + errno = EINVAL; + return -1; + } + // The write goes through the same counted mapping the rest of the API uses, so a + // caller that already holds the buffer mapped does not get a second mapping (or + // lose its own) behind its back. + Uint32 stride = 0; + void* address = GbmBufferMap(*bo, 0, 0, stride); + if (address == nullptr) + { + return -1; + } + memcpy(address, buf, count); + GbmBufferUnmap(*bo, address); + return 0; +} + +extern "C" void gbm_bo_set_user_data(struct gbm_bo* bo, void* data, + void (*destroy_user_data)(struct gbm_bo*, void*)) +{ + std::mutex* mutex = MutexOf(bo); + if (mutex == nullptr) + { + return; + } + std::lock_guard guard(*mutex); + // Assigning over existing data does not call the old callback: the record calls + // it exactly once, when the buffer goes away, which is the one moment a caller + // can still recognise the pointer it registered. + bo->Impl.UserData = data; + bo->Impl.UserDataDestroy = destroy_user_data; +} + +extern "C" void* gbm_bo_get_user_data(struct gbm_bo* bo) +{ + std::mutex* mutex = MutexOf(bo); + if (mutex == nullptr) + { + errno = EINVAL; + return nullptr; + } + std::lock_guard guard(*mutex); + return bo->Impl.UserData; +} + +extern "C" void gbm_bo_destroy(struct gbm_bo* bo) +{ + std::mutex* mutex = MutexOf(bo); + if (mutex == nullptr) + { + return; + } + std::lock_guard guard(*mutex); + // Drops this caller's reference. A buffer a surface still holds stays alive - + // that is what the surface's own reference is for - so destroying a buffer that + // is currently presented is not a use-after-free waiting to happen. + GbmBufferRelease(*bo->Impl.Owner, *bo); +} + +// --------------------------------------------------------------------------- +// Surfaces +// --------------------------------------------------------------------------- + +extern "C" struct gbm_surface* gbm_surface_create(struct gbm_device* gbm, uint32_t width, uint32_t height, + uint32_t format, uint32_t flags) +{ + std::mutex* mutex = MutexOf(gbm); + if (mutex == nullptr) + { + errno = EINVAL; + return nullptr; + } + std::lock_guard guard(*mutex); + return GbmSurfaceCreate(*gbm, width, height, format, flags, nullptr, 0); +} + +extern "C" struct gbm_surface* gbm_surface_create_with_modifiers(struct gbm_device* gbm, uint32_t width, + uint32_t height, uint32_t format, + const uint64_t* modifiers, + const unsigned int count) +{ + // As with the buffer pair: the modifier-less form is the flags-0 case. + return gbm_surface_create_with_modifiers2(gbm, width, height, format, modifiers, count, 0); +} + +extern "C" struct gbm_surface* gbm_surface_create_with_modifiers2(struct gbm_device* gbm, uint32_t width, + uint32_t height, uint32_t format, + const uint64_t* modifiers, + const unsigned int count, uint32_t flags) +{ + std::mutex* mutex = MutexOf(gbm); + if (mutex == nullptr) + { + errno = EINVAL; + return nullptr; + } + std::lock_guard guard(*mutex); + return GbmSurfaceCreate(*gbm, width, height, format, flags, modifiers, (Uint32)count); +} + +extern "C" struct gbm_bo* gbm_surface_lock_front_buffer(struct gbm_surface* surface) +{ + std::mutex* mutex = MutexOf(surface); + if (mutex == nullptr) + { + errno = EINVAL; + return nullptr; + } + std::lock_guard guard(*mutex); + // Null with errno = EBUSY when the compositor still holds every buffer: that is a + // state the caller has to handle, and pretending otherwise by handing out a + // buffer that is still being scanned out would be a torn frame rather than an + // error. + return GbmSurfaceLockFront(*surface); +} + +extern "C" void gbm_surface_release_buffer(struct gbm_surface* surface, struct gbm_bo* bo) +{ + std::mutex* mutex = MutexOf(surface); + if (mutex == nullptr || bo == nullptr) + { + return; + } + std::lock_guard guard(*mutex); + GbmSurfaceReleaseBuffer(*surface, *bo); +} + +extern "C" int gbm_surface_has_free_buffers(struct gbm_surface* surface) +{ + std::mutex* mutex = MutexOf(surface); + if (mutex == nullptr) + { + errno = EINVAL; + return 0; + } + std::lock_guard guard(*mutex); + // Truthful by construction: the swapchain exists from the moment the surface + // does, so this answers about buffers that were really allocated rather than + // about how many could still be. + return GbmSurfaceHasFreeBuffers(*surface) ? 1 : 0; +} + +extern "C" void gbm_surface_destroy(struct gbm_surface* surface) +{ + std::mutex* mutex = MutexOf(surface); + if (mutex == nullptr) + { + return; + } + std::lock_guard guard(*mutex); + GbmSurfaceDestroy(*surface); +} + +// --------------------------------------------------------------------------- +// Format names +// --------------------------------------------------------------------------- + +extern "C" char* gbm_format_get_name(uint32_t gbm_format, struct gbm_format_name_desc* desc) +{ + if (desc == nullptr) + { + errno = EINVAL; + return nullptr; + } + // The name of a fourcc is its four characters, most significant byte last, and + // that is all there is to it: a caller compares these strings against "AR24" and + // friends, and the big-endian bit is part of the value rather than something to + // spell out here. + desc->name[0] = (char)(gbm_format & 0xffu); + desc->name[1] = (char)((gbm_format >> 8) & 0xffu); + desc->name[2] = (char)((gbm_format >> 16) & 0xffu); + desc->name[3] = (char)((gbm_format >> 24) & 0xffu); + desc->name[4] = '\0'; + return desc->name; +} + +// End of Source File Header diff --git a/MobileGL/MG_Gbm/GbmDevice.cpp b/MobileGL/MG_Gbm/GbmDevice.cpp new file mode 100644 index 00000000..935da491 --- /dev/null +++ b/MobileGL/MG_Gbm/GbmDevice.cpp @@ -0,0 +1,1128 @@ +// MobileGL - MobileGL/MG_Gbm/GbmDevice.cpp +// SPDX-License-Identifier: LGPL-3.0-only +// +// The records, and the backend that allocates without a display server behind it. +// +// Allocation happens in the kernel's dma-heap when a heap node is readable by this +// user, and in a memfd when it is not. The order is not a preference: a dma-heap +// buffer is a real dma-buf, so the Anland display producer can import the fd and +// hand it to the phone, while a memfd is ordinary anonymous memory that no DRM +// device can import. The fallback exists so that a compositor still starts and +// renders on a machine where /dev/dma_heap is root-only or absent; it is announced +// in the log and in gbm_device_get_backend_name, because a frame that reaches the +// phone through the wrong path must never look like the right one. +// +// Every buffer this file produces is linear: one allocation, one stride per plane, +// no tiling and no compression, which is what gbm_bo_get_modifier answers with +// DRM_FORMAT_MOD_LINEAR. That is a statement about the memory that actually exists, +// not a default that was picked because nothing else was available - the dma-heap +// hands out contiguous system memory and this library never asks a GPU driver to +// lay anything out. + +#include "GbmInternal.h" + +#include +#include +#include +#include + +#include + +// memfd_create is declared by glibc under _GNU_SOURCE (which g++ defines for C++), +// but MFD_CLOEXEC lives in , which is not pulled in by +// and does not exist in every sysroot this file may be compiled against. +#ifndef MFD_CLOEXEC +#define MFD_CLOEXEC 0x0001U +#endif + +namespace MobileGL::MG_Gbm +{ + namespace + { + // Three buffers per swapchain: one on screen, one being rendered into, and + // one being handed back. That is the depth every compositor in this tree + // already assumes, and a fixed depth is what lets + // gbm_surface_has_free_buffers tell the truth instead of describing an + // unbounded pool that does not exist. + constexpr Uint32 GbmSurfaceBufferCount = 3; + + // The page size is not a compile-time constant on every target this library + // is built for (aarch64 kernels run 4K, 16K and 64K pages), and dma-heap + // rejects a length that is not a whole number of pages, so it is asked for. + SizeT GbmPageSize() noexcept + { + const long size = sysconf(_SC_PAGESIZE); + return size > 0 ? (SizeT)size : (SizeT)4096; + } + + SizeT GbmAlignUp(SizeT value, SizeT alignment) noexcept + { + if (alignment == 0) + { + return value; + } + return ((value + alignment - 1) / alignment) * alignment; + } + + // close() is allowed to overwrite errno, and every caller here is in the + // middle of reporting a failure with strerror(errno). Saving errno around + // the close is what keeps the reported failure the one that happened. + void GbmCloseQuietly(Int fd) noexcept + { + if (fd < 0) + { + return; + } + const Int savedErrno = errno; + close(fd); + errno = savedErrno; + } + + // The descriptor a caller receives must be its own - that is the whole + // difference between gbm_bo_get_fd and gbm_bo_get_handle - and a descriptor + // that survives exec into a child that has no idea what it refers to is + // worse than one that does not, so every dup here is close-on-exec. + Int GbmDupCloexec(Int fd) noexcept + { + if (fd < 0) + { + errno = EBADF; + return -1; + } + return fcntl(fd, F_DUPFD_CLOEXEC, 0); + } + + // The kernel's dma-heap allocator ABI: four fields and one ioctl number. + // They are spelled out here rather than included from + // because that header is absent from bionic and from plenty of desktop + // kernel-header packages, while the ABI itself has not changed since the + // allocator was merged. + struct DmaHeapAllocationData + { + Uint64 Len; + Uint32 Fd; + Uint32 FdFlags; + Uint64 HeapFlags; + }; + + constexpr Uint32 DmaHeapIoctlAlloc = (Uint32)_IOWR('H', 0x0, DmaHeapAllocationData); + + // One allocation, from the heap node when there is one and from a memfd when + // there is not. Returns the descriptor with the access mode the callers need + // (O_RDWR, so that mmap with PROT_WRITE works on either backend) and reports + // which backend answered in backendOut. + Int GbmAllocateBackingFd(GbmDevice& device, SizeT size, GbmBackend& backendOut) noexcept + { + const SizeT alignedSize = GbmAlignUp(size, GbmPageSize()); + + if (device.HeapFd >= 0) + { + DmaHeapAllocationData request = {}; + request.Len = (Uint64)alignedSize; + request.Fd = 0; // the kernel writes the new descriptor here + request.FdFlags = (Uint32)(O_RDWR | O_CLOEXEC); + request.HeapFlags = 0; + if (ioctl(device.HeapFd, (unsigned long)DmaHeapIoctlAlloc, &request) == 0) + { + backendOut = GbmBackend::DmaHeap; + return (Int)request.Fd; + } + const Int heapErrno = errno; + MGLOG_W_ONCE("gbm: %s refused a %zu byte allocation (%d: %s); this buffer falls back to a " + "memfd, which is not a dma-buf any DRM device or display host can import", + device.HeapPath.c_str(), alignedSize, heapErrno, strerror(heapErrno)); + } + + const Int fd = memfd_create("mobilegl-gbm", MFD_CLOEXEC); + if (fd < 0) + { + MGLOG_E_ONCE("gbm: memfd_create failed (%d: %s) and no dma-heap node is usable, so no " + "buffer can be allocated at all", + errno, strerror(errno)); + return -1; + } + if (ftruncate(fd, (off_t)alignedSize) != 0) + { + const Int truncateErrno = errno; + GbmCloseQuietly(fd); + errno = truncateErrno; + MGLOG_E_ONCE("gbm: ftruncate(%zu) on the fallback memfd failed (%d: %s)", alignedSize, errno, + strerror(errno)); + return -1; + } + backendOut = GbmBackend::MemFd; + return fd; + } + + // A modifier list that does not contain DRM_FORMAT_MOD_LINEAR asks for + // something this library cannot produce: it only ever allocates linear + // memory. Refusing outright would be worse than answering, because the + // caller re-reads gbm_bo_get_modifier and can still make its own decision + // about a linear buffer - so the request is served with the layout that + // exists and the disagreement is logged once, at the call site that caused it. + void GbmWarnIfLinearNotOffered(const Uint64* modifiers, Uint32 modifierCount, const char* what) noexcept + { + if (modifiers == nullptr || modifierCount == 0) + { + return; + } + for (Uint32 i = 0; i < modifierCount; ++i) + { + if (modifiers[i] == DrmFormatModLinear) + { + return; + } + } + MGLOG_W_ONCE("gbm: %s was offered %u modifier(s) that do not include DRM_FORMAT_MOD_LINEAR; " + "this library only allocates linear dma-bufs and reports " + "DRM_FORMAT_MOD_LINEAR, so the caller must accept the buffer it gets or refuse it", + what, modifierCount); + } + + // How many bytes an imported buffer's descriptors actually back. The caller + // described the memory with strides and offsets and the format says how many + // rows each plane has; where the format is one this library cannot lay out, + // the whole height is assumed for every plane. Over-stating is the safe + // direction: a mapping shorter than the caller's own stride arithmetic would + // fault on the last row. + SizeT GbmComputeImportSize(Uint32 format, Uint32 width, Uint32 height, const Uint32 strides[], + const Uint32 offsets[], Int planeCount) noexcept + { + GbmPlaneLayout layout[GbmMaxPlanes] = {}; + Int layoutPlanes = 0; + SizeT laidOutBytes = 0; + const Bool layoutKnown = GbmDescribeFormat(format, width, height, layout, layoutPlanes, laidOutBytes); + + SizeT end = 0; + for (Int i = 0; i < planeCount; ++i) + { + Uint32 rows = height; + if (layoutKnown && i < layoutPlanes && layout[i].Stride != 0) + { + rows = (Uint32)(layout[i].Size / layout[i].Stride); + } + const SizeT planeEnd = (SizeT)offsets[i] + (SizeT)strides[i] * (SizeT)rows; + if (planeEnd > end) + { + end = planeEnd; + } + } + return end; + } + } // namespace + + // ------------------------------------------------------------------------ + // Formats + // ------------------------------------------------------------------------ + + Bool GbmDescribeFormat(Uint32 format, Uint32 width, Uint32 height, GbmPlaneLayout planes[GbmMaxPlanes], + Int& planeCount, SizeT& totalBytes) noexcept + { + if (width == 0 || height == 0) + { + return false; + } + + struct PlaneShape + { + Uint32 BytesPerPixel; + Uint32 Width; + Uint32 Height; + }; + + PlaneShape shape[GbmMaxPlanes] = {{0, 0, 0}, {0, 0, 0}, {0, 0, 0}, {0, 0, 0}}; + Int count = 1; + + switch (format) + { + // ---- one plane, an integer number of bytes per pixel ---- + case GBM_FORMAT_C8: + case GBM_FORMAT_R8: + case GBM_FORMAT_RGB332: + case GBM_FORMAT_BGR233: + shape[0] = {1, width, height}; + break; + + case GBM_FORMAT_R16: + case GBM_FORMAT_GR88: + case GBM_FORMAT_XRGB4444: + case GBM_FORMAT_XBGR4444: + case GBM_FORMAT_RGBX4444: + case GBM_FORMAT_BGRX4444: + case GBM_FORMAT_ARGB4444: + case GBM_FORMAT_ABGR4444: + case GBM_FORMAT_RGBA4444: + case GBM_FORMAT_BGRA4444: + case GBM_FORMAT_XRGB1555: + case GBM_FORMAT_XBGR1555: + case GBM_FORMAT_RGBX5551: + case GBM_FORMAT_BGRX5551: + case GBM_FORMAT_ARGB1555: + case GBM_FORMAT_ABGR1555: + case GBM_FORMAT_RGBA5551: + case GBM_FORMAT_BGRA5551: + case GBM_FORMAT_RGB565: + case GBM_FORMAT_BGR565: + shape[0] = {2, width, height}; + break; + + case GBM_FORMAT_RGB888: + case GBM_FORMAT_BGR888: + shape[0] = {3, width, height}; + break; + + case GBM_FORMAT_RG1616: + case GBM_FORMAT_GR1616: + case GBM_FORMAT_XRGB8888: + case GBM_FORMAT_XBGR8888: + case GBM_FORMAT_RGBX8888: + case GBM_FORMAT_BGRX8888: + case GBM_FORMAT_ARGB8888: + case GBM_FORMAT_ABGR8888: + case GBM_FORMAT_RGBA8888: + case GBM_FORMAT_BGRA8888: + case GBM_FORMAT_XRGB2101010: + case GBM_FORMAT_XBGR2101010: + case GBM_FORMAT_RGBX1010102: + case GBM_FORMAT_BGRX1010102: + case GBM_FORMAT_ARGB2101010: + case GBM_FORMAT_ABGR2101010: + case GBM_FORMAT_RGBA1010102: + case GBM_FORMAT_BGRA1010102: + case GBM_FORMAT_AYUV: + shape[0] = {4, width, height}; + break; + + case GBM_FORMAT_XBGR16161616: + case GBM_FORMAT_ABGR16161616: + case GBM_FORMAT_XBGR16161616F: + case GBM_FORMAT_ABGR16161616F: + shape[0] = {8, width, height}; + break; + + // Packed 4:2:2: one 32 bit word holds two pixels, so a row is two bytes per + // pixel even though no single pixel is two bytes wide. + case GBM_FORMAT_YUYV: + case GBM_FORMAT_YVYU: + case GBM_FORMAT_UYVY: + case GBM_FORMAT_VYUY: + shape[0] = {2, width, height}; + break; + + // ---- two planes ---- + // NV12/NV21 interleave Cb and Cr, so one chroma row is one row of pairs and + // is as wide as the luma row in bytes; only its row count is halved. NV16 + // and NV61 subsample horizontally only, so their chroma rows match luma. + case GBM_FORMAT_NV12: + case GBM_FORMAT_NV21: + shape[0] = {1, width, height}; + shape[1] = {1, width, (height + 1) / 2}; + count = 2; + break; + + case GBM_FORMAT_NV16: + case GBM_FORMAT_NV61: + shape[0] = {1, width, height}; + shape[1] = {1, width, height}; + count = 2; + break; + + // ---- three planes ---- + // The chroma planes are rounded up rather than truncated: for an odd width or + // height the last chroma sample still has to fit, and a plane one row short + // is a plane the GPU writes past the end of. + case GBM_FORMAT_YUV420: + case GBM_FORMAT_YVU420: + shape[0] = {1, width, height}; + shape[1] = {1, (width + 1) / 2, (height + 1) / 2}; + shape[2] = shape[1]; + count = 3; + break; + + case GBM_FORMAT_YUV410: + case GBM_FORMAT_YVU410: + shape[0] = {1, width, height}; + shape[1] = {1, (width + 3) / 4, (height + 3) / 4}; + shape[2] = shape[1]; + count = 3; + break; + + case GBM_FORMAT_YUV411: + case GBM_FORMAT_YVU411: + shape[0] = {1, width, height}; + shape[1] = {1, (width + 3) / 4, height}; + shape[2] = shape[1]; + count = 3; + break; + + case GBM_FORMAT_YUV422: + case GBM_FORMAT_YVU422: + shape[0] = {1, width, height}; + shape[1] = {1, (width + 1) / 2, height}; + shape[2] = shape[1]; + count = 3; + break; + + case GBM_FORMAT_YUV444: + case GBM_FORMAT_YVU444: + shape[0] = {1, width, height}; + shape[1] = {1, width, height}; + shape[2] = shape[1]; + count = 3; + break; + + // A format this library cannot lay out is refused rather than guessed at: + // every plane offset that follows from a wrong table is memory some other + // component will write through. + default: + return false; + } + + // Planes are packed tightly, each one starting where the previous ended. That + // is the layout a single-stride-per-plane reader expects, and every plane of + // the buffer stays addressable from its offset inside the one allocation. + SizeT offset = 0; + for (Int i = 0; i < count; ++i) + { + const SizeT stride = (SizeT)shape[i].Width * shape[i].BytesPerPixel; + const SizeT size = stride * shape[i].Height; + // A plane offset is a 32 bit field in every DRM descriptor that follows, + // so a buffer whose planes would not fit in one is not one this library + // can describe. + if (stride > 0xffffffffu || offset + size > 0xffffffffu) + { + return false; + } + planes[i].Stride = (Uint32)stride; + planes[i].Offset = (Uint32)offset; + planes[i].Size = size; + offset += size; + } + for (Int i = count; i < (Int)GbmMaxPlanes; ++i) + { + planes[i] = GbmPlaneLayout{}; + } + + planeCount = count; + totalBytes = offset; + return true; + } + + Uint32 GbmFormatBytesPerPixel(Uint32 format) noexcept + { + GbmPlaneLayout planes[GbmMaxPlanes] = {}; + Int planeCount = 0; + SizeT totalBytes = 0; + // A 1x1 image of the format has exactly one pixel per row, so its plane 0 + // stride is the format's bytes per pixel. Asking the layout function keeps + // one table in this file instead of two that can drift apart. + if (!GbmDescribeFormat(format, 1, 1, planes, planeCount, totalBytes)) + { + return 0; + } + return planes[0].Stride; + } + + Bool GbmFormatIsSupported(Uint32 format) noexcept + { + return GbmFormatBytesPerPixel(format) != 0; + } + + // ------------------------------------------------------------------------ + // Record lifetime + // ------------------------------------------------------------------------ + + GbmBufferObject::~GbmBufferObject() + { + // A buffer that is still mapped when it is destroyed is unmapped here rather + // than leaked: gbm_bo_destroy promises to release the buffer, and a + // compositor that forgot an unmap must not keep the pages of a dead dma-buf + // alive. This is also why the deliberate "leave the write mapping open" + // habit some callers have is safe with this library. + if (MapAddress != nullptr) + { + munmap(MapAddress, MapSize); + MapAddress = nullptr; + MapSize = 0; + MapRefCount = 0; + } + for (Uint32 i = 0; i < GbmMaxPlanes; ++i) + { + if (Fds[i] >= 0) + { + close(Fds[i]); + Fds[i] = -1; + } + } + if (UserDataDestroy != nullptr) + { + // The public name of this record is the gbm_bo that contains it, which is + // the pointer the caller registered the data against. + UserDataDestroy(reinterpret_cast<::gbm_bo*>(this), UserData); + UserDataDestroy = nullptr; + UserData = nullptr; + } + } + + GbmSurface::~GbmSurface() + { + if (Owner == nullptr) + { + return; + } + // Dropping the surface's own references here is what removes a second + // failure path from surface creation: a swapchain that could only allocate + // two of its three buffers returns those two to the device by unwinding, + // without a cleanup routine of its own. A buffer the compositor still holds + // locked survives, because the reference the lock handed out is separate. + for (::gbm_bo* buffer : Buffers) + { + if (buffer != nullptr) + { + GbmBufferRelease(*Owner, *buffer); + } + } + Buffers.clear(); + Free.clear(); + LockedIndex = -1; + Owner = nullptr; + } + + GbmDevice::~GbmDevice() + { + if (HeapFd >= 0) + { + close(HeapFd); + HeapFd = -1; + } + if (DeviceFd >= 0) + { + close(DeviceFd); + DeviceFd = -1; + } + } + + // ------------------------------------------------------------------------ + // Device + // ------------------------------------------------------------------------ + + ::gbm_device* GbmDeviceCreate(Int fd) noexcept + { + ::gbm_device* device = nullptr; + try + { + device = new gbm_device(); + GbmDevice& impl = device->Impl; + + // The caller's descriptor is duped rather than adopted. gbm_create_device + // is called with an fd the caller usually keeps using - kwin holds its + // DRM node open for mode setting for the life of the process - and a + // device that closed it in gbm_device_destroy would be closing somebody + // else's descriptor. The dup costs one number and removes the question. + if (fd >= 0) + { + impl.DeviceFd = GbmDupCloexec(fd); + if (impl.DeviceFd < 0) + { + MGLOG_W("gbm_create_device: could not dup fd %d (%d: %s); the device is still usable " + "because the self-allocated backend needs no DRM node", + fd, errno, strerror(errno)); + } + } + else + { + // gbm_create_device(-1) asks for a device with no node behind it. A + // render node is opened when there is one, so that gbm_device_get_fd + // answers with something a caller can use, but failing to open it is + // not fatal: nothing in the self-allocated backend talks to DRM. + impl.DeviceFd = open("/dev/dri/renderD128", O_RDWR | O_CLOEXEC); + if (impl.DeviceFd < 0) + { + MGLOG_I("gbm_create_device(-1): no render node to open (%d: %s); allocating from the " + "dma-heap without one", errno, strerror(errno)); + } + } + + // The heap nodes, in the order the container is expected to provide + // them. The uncached node is the fallback for a machine that exposes only + // it; a cached node is what a compositor wants, because it renders into + // these buffers with the GPU. + static const char* const heapNodes[] = {"/dev/dma_heap/system", "/dev/dma_heap/system-uncached"}; + for (const char* node : heapNodes) + { + const Int heapFd = open(node, O_RDWR | O_CLOEXEC); + if (heapFd >= 0) + { + impl.HeapFd = heapFd; + impl.HeapPath = node; + break; + } + MGLOG_I("gbm_create_device: %s is not usable (%d: %s)", node, errno, strerror(errno)); + } + + impl.DefaultBackend = impl.HeapFd >= 0 ? GbmBackend::DmaHeap : GbmBackend::MemFd; + // The backend name is not decoration: a compositor that logs it, and the + // T4 probe that records it, are asking which kind of memory the frames + // they are about to show are made of. + impl.BackendName = impl.HeapFd >= 0 ? "mobilegl-dma-heap" : "mobilegl-memfd"; + + if (impl.HeapFd >= 0) + { + MGLOG_I("gbm_create_device: %s backend=%s devFd=%d heapFd=%d (buffers are dma-bufs, " + "modifier=DRM_FORMAT_MOD_LINEAR)", + impl.HeapPath.c_str(), impl.BackendName.c_str(), impl.DeviceFd, impl.HeapFd); + } + else + { + MGLOG_W("gbm_create_device: no dma-heap node could be opened, so every buffer comes from a " + "memfd; backend=%s devFd=%d. Rendering still works, but the fds cannot be imported " + "by a DRM device or shipped to the phone as dma-bufs until %s is readable", + impl.BackendName.c_str(), impl.DeviceFd, heapNodes[0]); + } + + // The host-frame backend is not part of this build: the channel object + // stays null and HostAvailable stays false, which is exactly the state + // the allocation paths treat as "allocate locally". + impl.HostAvailable = false; + impl.Host = nullptr; + impl.SocketPath = ""; + + return device; + } + catch (...) + { + // A GBM device that throws cannot be caught by any of its C callers, so + // the failure is reported the way GBM reports every other one. + delete device; + errno = ENOMEM; + return nullptr; + } + } + + void GbmDeviceDestroy(::gbm_device& device) noexcept + { + GbmDevice& impl = device.Impl; + + // Surfaces first. A surface holds a reference to every buffer in its + // swapchain, so destroying a buffer before its surface would leave the + // surface's table pointing at memory that is already gone. + while (!impl.Surfaces.empty()) + { + GbmSurfaceDestroy(*impl.Surfaces.begin()->first); + } + + if (!impl.Buffers.empty()) + { + // A process that exits without destroying its buffers is the normal case + // (the compositor relies on process exit for the last few), so this is a + // statement about what is being released here, not a call to action the + // caller can still answer. + MGLOG_W("gbm_device_destroy: %zu buffer(s) were still allocated; releasing them here rather " + "than letting the dma-bufs outlive the device that owns them", + impl.Buffers.size()); + } + while (!impl.Buffers.empty()) + { + GbmBufferForceDestroy(device, *impl.Buffers.begin()->first); + } + + // The destructor closes the heap node and the private device fd. + delete &device; + } + + // ------------------------------------------------------------------------ + // Buffers + // ------------------------------------------------------------------------ + + ::gbm_bo* GbmBufferCreate(::gbm_device& device, Uint32 width, Uint32 height, Uint32 format, Uint64 usage, + const Uint64* modifiers, Uint32 modifierCount) noexcept + { + if (width == 0 || height == 0) + { + errno = EINVAL; + return nullptr; + } + if ((usage & GBM_BO_USE_PROTECTED) != 0) + { + // A protected buffer is encrypted memory that only a trusted + // display/GPU path may touch. A dma-heap allocation is plain system + // memory that any process holding the fd can map, so answering this + // request with one would hand out the opposite of what was asked for. + MGLOG_E_ONCE("gbm_bo_create: GBM_BO_USE_PROTECTED is not something a dma-heap allocation can " + "provide; refusing rather than handing out unprotected memory"); + errno = EINVAL; + return nullptr; + } + + GbmPlaneLayout layout[GbmMaxPlanes] = {}; + Int planeCount = 0; + SizeT totalBytes = 0; + if (!GbmDescribeFormat(format, width, height, layout, planeCount, totalBytes)) + { + MGLOG_E_ONCE("gbm_bo_create: format 0x%08x is not a format this library can lay out; refusing " + "rather than allocating memory whose plane offsets would be a guess", + format); + errno = EINVAL; + return nullptr; + } + GbmWarnIfLinearNotOffered(modifiers, modifierCount, "gbm_bo_create_with_modifiers"); + + ::gbm_bo* bo = nullptr; + try + { + GbmBackend backend = GbmBackend::None; + const Int fd = GbmAllocateBackingFd(device.Impl, totalBytes, backend); + if (fd < 0) + { + errno = ENOMEM; + return nullptr; + } + + // From here on the record owns the descriptor, so even the throwing + // paths below cannot leak it: the holder's destructor closes it. + bo = new gbm_bo(); + GbmBufferObject& impl = bo->Impl; + impl.Width = width; + impl.Height = height; + impl.Format = format; + impl.Stride = layout[0].Stride; + impl.BytesPerPixel = layout[0].Stride / width; + impl.Usage = usage; + impl.PlaneCount = planeCount; + impl.AllocationSize = GbmAlignUp(totalBytes, GbmPageSize()); + impl.Backend = backend; + impl.Owner = &device; + impl.RefCount = 1; + impl.Fds[0] = fd; + impl.Modifier = DrmFormatModLinear; + for (Int i = 0; i < (Int)GbmMaxPlanes; ++i) + { + const Bool used = i < planeCount; + impl.Strides[i] = used ? layout[i].Stride : 0; + impl.Offsets[i] = used ? layout[i].Offset : 0; + impl.Modifiers[i] = DrmFormatModLinear; + } + + // The device's table is the record of what is alive, so registering + // before the function returns is what makes gbm_device_destroy able to + // release a buffer whose owner forgot it. + device.Impl.Buffers[bo] = &impl; + + MGLOG_I("gbm_bo_create: %ux%u format=0x%08x stride=%u planes=%d size=%zu fd=%d backend=%s " + "modifier=DRM_FORMAT_MOD_LINEAR(0)", + width, height, format, impl.Stride, planeCount, impl.AllocationSize, fd, + backend == GbmBackend::DmaHeap ? "dma-heap" : "memfd"); + return bo; + } + catch (...) + { + delete bo; + errno = ENOMEM; + return nullptr; + } + } + + ::gbm_bo* GbmBufferImport(::gbm_device& device, Uint32 type, const void* buffer, Uint64 usage) noexcept + { + if (buffer == nullptr) + { + errno = EINVAL; + return nullptr; + } + + Uint32 width = 0; + Uint32 height = 0; + Uint32 format = 0; + Int planeCount = 1; + Uint64 modifier = DrmFormatModLinear; + Uint32 strides[GbmMaxPlanes] = {0, 0, 0, 0}; + Uint32 offsets[GbmMaxPlanes] = {0, 0, 0, 0}; + Int sourceFds[GbmMaxPlanes] = {-1, -1, -1, -1}; + + switch (type) + { + case GBM_BO_IMPORT_FD: + { + const auto* data = static_cast(buffer); + width = data->width; + height = data->height; + format = data->format; + strides[0] = data->stride; + sourceFds[0] = data->fd; + planeCount = 1; + // A plain FD import carries a stride and nothing else, and a single + // stride is only a complete description of a linear image - so linear is + // what this records. A caller importing a tiled or compressed buffer has + // to say so, and the import type that can say it is + // GBM_BO_IMPORT_FD_MODIFIER. + modifier = DrmFormatModLinear; + break; + } + case GBM_BO_IMPORT_FD_MODIFIER: + { + const auto* data = static_cast(buffer); + if (data->num_fds == 0 || data->num_fds > GBM_MAX_PLANES) + { + MGLOG_E_ONCE("gbm_bo_import: GBM_BO_IMPORT_FD_MODIFIER with %u descriptors; this library " + "handles 1..%d", data->num_fds, (int)GBM_MAX_PLANES); + errno = EINVAL; + return nullptr; + } + width = data->width; + height = data->height; + format = data->format; + planeCount = (Int)data->num_fds; + // The modifier is the caller's own statement about memory it allocated, + // so it is carried through unchanged rather than replaced with the one + // this library's own allocations use. + modifier = data->modifier; + for (Int i = 0; i < planeCount; ++i) + { + sourceFds[i] = data->fds[i]; + strides[i] = (Uint32)data->strides[i]; + offsets[i] = (Uint32)data->offsets[i]; + } + break; + } + default: + // A wl_buffer or an EGLImage is a handle this library cannot resolve + // without the client library that minted it, and guessing would produce + // a buffer pointing at memory nobody verified. + MGLOG_E_ONCE("gbm_bo_import: type 0x%08x is not implemented; only GBM_BO_IMPORT_FD and " + "GBM_BO_IMPORT_FD_MODIFIER are, because any other type names a handle this " + "library has no way to resolve", type); + errno = ENOSYS; + return nullptr; + } + + if (width == 0 || height == 0) + { + errno = EINVAL; + return nullptr; + } + + // Every descriptor is duped before the record exists, so a failure half way + // leaves none of the caller's descriptors closed and none of ours open. The + // import owns its copies: the peer that mints a dma-buf and closes its own + // fd right after the import is doing exactly what this contract allows. + Int ownedFds[GbmMaxPlanes] = {-1, -1, -1, -1}; + for (Int i = 0; i < planeCount; ++i) + { + ownedFds[i] = GbmDupCloexec(sourceFds[i]); + if (ownedFds[i] < 0) + { + const Int dupErrno = errno; + for (Int j = 0; j < planeCount; ++j) + { + GbmCloseQuietly(ownedFds[j]); + } + MGLOG_E_ONCE("gbm_bo_import: cannot dup plane %d's descriptor (%d: %s)", i, dupErrno, + strerror(dupErrno)); + errno = dupErrno; + return nullptr; + } + } + + const SizeT totalBytes = GbmComputeImportSize(format, width, height, strides, offsets, planeCount); + + ::gbm_bo* bo = nullptr; + try + { + bo = new gbm_bo(); + GbmBufferObject& impl = bo->Impl; + impl.Width = width; + impl.Height = height; + impl.Format = format; + impl.Stride = strides[0]; + impl.BytesPerPixel = GbmFormatBytesPerPixel(format); + impl.Usage = usage; + impl.PlaneCount = planeCount; + impl.AllocationSize = GbmAlignUp(totalBytes, GbmPageSize()); + impl.Backend = GbmBackend::None; + impl.Owner = &device; + impl.RefCount = 1; + impl.Modifier = modifier; + for (Int i = 0; i < (Int)GbmMaxPlanes; ++i) + { + const Bool used = i < planeCount; + impl.Fds[i] = ownedFds[i]; + impl.Strides[i] = used ? strides[i] : 0; + impl.Offsets[i] = used ? offsets[i] : 0; + impl.Modifiers[i] = used ? modifier : 0; + } + + device.Impl.Buffers[bo] = &impl; + + MGLOG_I("gbm_bo_import: type=0x%08x %ux%u format=0x%08x stride=%u planes=%d modifier=0x%016llx " + "size=%zu", + type, width, height, format, impl.Stride, planeCount, (unsigned long long)modifier, + impl.AllocationSize); + return bo; + } + catch (...) + { + for (Int i = 0; i < planeCount; ++i) + { + GbmCloseQuietly(ownedFds[i]); + } + delete bo; + errno = ENOMEM; + return nullptr; + } + } + + void GbmBufferRelease(::gbm_device& device, ::gbm_bo& bo) noexcept + { + GbmBufferObject& impl = bo.Impl; + if (impl.RefCount > 1) + { + impl.RefCount--; + return; + } + // The last reference is gone, so the record goes with it - and the record's + // destructor is what closes the descriptors and releases any live mapping. + device.Impl.Buffers.erase(&bo); + delete &bo; + } + + void GbmBufferForceDestroy(::gbm_device& device, ::gbm_bo& bo) noexcept + { + device.Impl.Buffers.erase(&bo); + delete &bo; + } + + Int GbmBufferDupFd(::gbm_bo& bo, Int plane) noexcept + { + GbmBufferObject& impl = bo.Impl; + if (plane < 0) + { + plane = 0; + } + if (plane >= impl.PlaneCount) + { + errno = EINVAL; + return -1; + } + // A self-allocated buffer keeps every plane in one dma-buf and separates them + // by offset, so the same descriptor answers for each of its planes; an import + // that carried one descriptor per plane has its own. + const Int source = impl.Fds[plane] >= 0 ? impl.Fds[plane] : impl.Fds[0]; + return GbmDupCloexec(source); + } + + void* GbmBufferMap(::gbm_bo& bo, Uint32 x, Uint32 y, Uint32& strideOut) noexcept + { + GbmBufferObject& impl = bo.Impl; + if (impl.Fds[0] < 0 || impl.AllocationSize == 0) + { + MGLOG_E_ONCE("gbm_bo_map: the buffer carries no descriptor to map"); + errno = EBADF; + return nullptr; + } + if (x >= impl.Width || y >= impl.Height) + { + // A rectangle outside the buffer would produce an address outside the + // mapping, and the fault would land in the caller's code rather than in + // this one. + errno = EINVAL; + return nullptr; + } + + if (impl.MapAddress == nullptr) + { + // One mmap for the whole buffer, MAP_SHARED because the point of the + // exercise is that the CPU and the GPU look at the same memory: a private + // mapping would give the caller a copy that no scanout ever sees. The + // protection is read/write regardless of the transfer flags, because the + // mapping is shared between calls and a later write must not need a + // second one. + void* address = mmap(nullptr, impl.AllocationSize, PROT_READ | PROT_WRITE, MAP_SHARED, impl.Fds[0], 0); + if (address == MAP_FAILED) + { + MGLOG_E_ONCE("gbm_bo_map: mmap(%zu, PROT_READ|PROT_WRITE, MAP_SHARED) on fd %d failed " + "(%d: %s)", + impl.AllocationSize, impl.Fds[0], errno, strerror(errno)); + return nullptr; + } + impl.MapAddress = address; + impl.MapSize = impl.AllocationSize; + impl.MapRefCount = 0; + } + + // Counted rather than duplicated: a second map of the same buffer returns the + // address the first one did, and the mapping only goes away when the last + // unmap has been seen. + impl.MapRefCount++; + strideOut = impl.Strides[0]; + return static_cast(impl.MapAddress) + (SizeT)y * impl.Strides[0] + + (SizeT)x * impl.BytesPerPixel; + } + + void GbmBufferUnmap(::gbm_bo& bo, void* mapData) noexcept + { + GbmBufferObject& impl = bo.Impl; + if (impl.MapAddress == nullptr || impl.MapRefCount <= 0) + { + MGLOG_W_ONCE("gbm_bo_unmap: the buffer is not mapped; the call is ignored"); + return; + } + // map_data is accepted rather than required to match the base address: + // callers hand back either the pointer gbm_bo_map returned or the one it + // wrote into *map_data, and for a sub-rectangle those differ. Both name the + // same mapping, and the count is what decides when it is released. + (void)mapData; + impl.MapRefCount--; + if (impl.MapRefCount > 0) + { + return; + } + munmap(impl.MapAddress, impl.MapSize); + impl.MapAddress = nullptr; + impl.MapSize = 0; + } + + // ------------------------------------------------------------------------ + // Surfaces + // ------------------------------------------------------------------------ + + ::gbm_surface* GbmSurfaceCreate(::gbm_device& device, Uint32 width, Uint32 height, Uint32 format, Uint64 usage, + const Uint64* modifiers, Uint32 modifierCount) noexcept + { + if (width == 0 || height == 0 || !GbmFormatIsSupported(format)) + { + // The format is checked here rather than left to the first + // lock_front_buffer, because a surface that exists but cannot produce a + // buffer fails inside the compositor's frame loop, where there is no good + // answer left. + MGLOG_E("gbm_surface_create: %ux%u format 0x%08x is not something this library can allocate", + width, height, format); + errno = EINVAL; + return nullptr; + } + GbmWarnIfLinearNotOffered(modifiers, modifierCount, "gbm_surface_create"); + + ::gbm_surface* surface = nullptr; + try + { + surface = new gbm_surface(); + GbmSurface& impl = surface->Impl; + impl.Width = width; + impl.Height = height; + impl.Format = format; + impl.Flags = (Uint32)usage; + impl.Owner = &device; + impl.LockedIndex = -1; + impl.Buffers.reserve(GbmSurfaceBufferCount); + impl.Free.reserve(GbmSurfaceBufferCount); + + for (Uint32 i = 0; i < GbmSurfaceBufferCount; ++i) + { + ::gbm_bo* buffer = GbmBufferCreate(device, width, height, format, usage, modifiers, modifierCount); + if (buffer == nullptr) + { + // ~GbmSurface returns the buffers that did get allocated, so this + // path needs no cleanup of its own. + delete surface; + errno = ENOMEM; + return nullptr; + } + impl.Buffers.push_back(buffer); + impl.Free.push_back(true); + } + + device.Impl.Surfaces[surface] = &impl; + MGLOG_I("gbm_surface_create: %ux%u format=0x%08x flags=0x%x swapchain=%u buffers (all free)", + width, height, format, (unsigned)usage, GbmSurfaceBufferCount); + return surface; + } + catch (...) + { + delete surface; + errno = ENOMEM; + return nullptr; + } + } + + void GbmSurfaceDestroy(::gbm_surface& surface) noexcept + { + GbmSurface& impl = surface.Impl; + if (impl.Owner != nullptr) + { + impl.Owner->Impl.Surfaces.erase(&surface); + } + // ~GbmSurface returns the swapchain's references. A buffer the compositor + // still holds stays alive until it releases or destroys it, which is exactly + // what the reference counts are for. + delete &surface; + } + + ::gbm_bo* GbmSurfaceLockFront(::gbm_surface& surface) noexcept + { + GbmSurface& impl = surface.Impl; + for (SizeT i = 0; i < impl.Buffers.size(); ++i) + { + if (!impl.Free[i]) + { + continue; + } + ::gbm_bo* buffer = impl.Buffers[i]; + impl.Free[i] = false; + impl.LockedIndex = (Int)i; + // The caller gets a reference of its own on top of the surface's. That is + // what makes gbm_bo_destroy on a locked buffer safe: it drops the caller's + // reference and leaves the surface's table intact. + buffer->Impl.RefCount++; + return buffer; + } + MGLOG_W_ONCE("gbm_surface_lock_front_buffer: every swapchain buffer is still locked; the compositor " + "has to release one before it can render into another"); + errno = EBUSY; + return nullptr; + } + + void GbmSurfaceReleaseBuffer(::gbm_surface& surface, ::gbm_bo& bo) noexcept + { + GbmSurface& impl = surface.Impl; + for (SizeT i = 0; i < impl.Buffers.size(); ++i) + { + if (impl.Buffers[i] != &bo) + { + continue; + } + if (impl.Free[i]) + { + // Releasing twice would hand the same buffer to two frames at once, + // so the second release is ignored and said out loud. + MGLOG_W_ONCE("gbm_surface_release_buffer: the buffer was already released; the second " + "release is ignored so that two frames cannot be handed the same buffer"); + return; + } + impl.Free[i] = true; + if (impl.LockedIndex == (Int)i) + { + impl.LockedIndex = -1; + } + // Drops the reference the lock handed out. The surface's own reference + // keeps the record - and this table's pointer to it - valid. + GbmBufferRelease(*impl.Owner, bo); + return; + } + // The pointer is compared, never followed, so a stale or foreign buffer + // reaches this line safely instead of being released. + MGLOG_W_ONCE("gbm_surface_release_buffer: the buffer was not handed out by this surface; ignored"); + } + + Bool GbmSurfaceHasFreeBuffers(::gbm_surface& surface) noexcept + { + for (const Bool free : surface.Impl.Free) + { + if (free) + { + return true; + } + } + return false; + } +} // namespace MobileGL::MG_Gbm + +// End of Source File Header diff --git a/MobileGL/MG_Gbm/GbmInternal.h b/MobileGL/MG_Gbm/GbmInternal.h new file mode 100644 index 00000000..ce3adaf2 --- /dev/null +++ b/MobileGL/MG_Gbm/GbmInternal.h @@ -0,0 +1,358 @@ +// MobileGL - MobileGL/MG_Gbm/GbmInternal.h +// SPDX-License-Identifier: LGPL-3.0-only +// +// This header carries the internal model shared by the GBM front-end files: the +// device, buffer-object and surface records, the two allocation backends, and the +// wire protocol used to talk to a phone that owns the real display hardware. +// +// The reason a GBM implementation has to exist in this tree at all is that the +// container half of MobileGL runs a Wayland compositor (kwin). That compositor +// needs libgbm to allocate the buffers it renders into, and the fd of such a +// buffer is handed to the Anland display producer, which ships the frame to the +// phone. If Mesa's libgbm is loaded, the compositor renders into Mesa buffers and +// the phone ends up receiving a copy through a path that was never designed to +// carry them. When this library is loaded instead, the buffers are either frames +// the phone itself offered to us, or buffers allocated from the kernel's dma-heap +// so that they are dma-bufs the display host can consume directly. +// +// The ownership rules below are the whole contract, because every one of them is +// observable from outside this library: +// +// * A buffer record owns its descriptors. gbm_bo_get_fd and +// gbm_bo_get_fd_for_plane hand out a dup, so a caller that closes what it was +// given cannot invalidate the buffer, and the matching close happens exactly +// once, in ~GbmBufferObject. +// * A mapping is one mmap of the buffer's fd, counted rather than duplicated, so +// two overlapping gbm_bo_map calls share one address and the last matching +// gbm_bo_unmap releases it. +// * A surface owns one reference per swapchain buffer, and every +// gbm_surface_lock_front_buffer hands the caller a reference of its own. That +// is what makes gbm_bo_destroy on a locked buffer harmless: it drops the +// caller's reference and leaves the surface's bookkeeping intact. +// * One mutex per device. GBM calls arrive from the compositor's render thread +// and from its main thread, and the state they touch (the buffer table, the +// swapchains, the backends) is small enough that one lock is cheaper than +// several - and, more importantly, one lock cannot be taken in the wrong order. +// +// The helpers declared at the bottom of this file are the entire internal interface +// between GbmDevice.cpp (the records and the self-allocated backend) and GbmApi.cpp +// (the extern "C" surface). Every one of them assumes the device mutex is already +// held: the lock is taken once, in the API layer, so that a compositor call can +// never interleave with another half way through a swapchain update. + +#pragma once + +#include + +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace MobileGL::MG_Gbm +{ + // DRM_FORMAT_MOD_LINEAR and DRM_FORMAT_MOD_INVALID. drm_fourcc.h is not part of + // libgbm's interface, and these two values are frozen kernel UAPI, so they are + // spelled out here rather than made a build dependency on libdrm's headers. + static constexpr Uint64 DrmFormatModLinear = 0ULL; + static constexpr Uint64 DrmFormatModInvalid = 0x00ffffffffffffffULL; + + // Every buffer this library hands out is described by one of these. HostFrame + // buffers are owned by the phone and only borrowed by us, so they must be + // released back over the channel instead of being freed locally. + enum class GbmBackend + { + None = 0, + HostFrame, + DmaHeap, + MemFd, + }; + + // The phone offers frames with this packed struct immediately followed by an + // AHardwareBuffer native handle in the ancillary data of the same message. + // The layout is fixed by the phone side, so the packing attribute is load + // bearing and the field order may not be rearranged. Nothing in this run reads + // or writes it: it is the hook the host-frame backend plugs into, kept here so + // that the wire format has one home and does not have to be re-derived. + struct GbmFrameOffer + { + uint32_t Magic; // 'M','G','G','F' little endian on both ends of the wire + uint32_t Version; + uint32_t Seq; + uint32_t Index; + uint32_t Width; + uint32_t Height; + uint32_t StrideBytes; + uint32_t Format; + uint32_t UsageLo; + uint32_t UsageHi; + } __attribute__((packed)); + + // The container replies with this once it no longer reads the frame, which is + // what lets the phone recycle the slot. Without it the display host would run + // out of buffers after a handful of frames. + struct GbmFrameRelease + { + uint32_t Magic; // 'M','G','G','R' + uint32_t Version; + uint32_t Seq; + } __attribute__((packed)); + + static constexpr uint32_t GbmFrameOfferMagic = 0x4647474Du; // "MGGF" + static constexpr uint32_t GbmFrameReleaseMagic = 0x5247474Du; // "MGGR" + static constexpr uint32_t GbmHostProtocolVersion = 1; + + // An AHardwareBuffer native handle as it appears on the wire: three counts + // followed by numFds file descriptors and numInts integers. The counts are + // 32 bit even on 64 bit Android, which is why they are spelled out here + // rather than using the platform's native_handle_t. + struct AHBNativeHandleHeader + { + int32_t Version; + int32_t NumFds; + int32_t NumInts; + } __attribute__((packed)); + + static constexpr uint32_t GbmMaxPlanes = 4; + + // How one plane of a buffer is laid out inside the single allocation that backs + // it. Multi-plane formats keep every plane in one dma-buf and describe the + // separation with Offsets, which is the layout a modifier of + // DRM_FORMAT_MOD_LINEAR can express and the only one this library produces. + struct GbmPlaneLayout + { + Uint32 Stride = 0; + Uint32 Offset = 0; + SizeT Size = 0; + }; + + // One imported or locally allocated buffer. The struct is private to this + // library; gbm_bo is the public opaque name and is defined in terms of it. + struct GbmBufferObject + { + Uint32 Width = 0; + Uint32 Height = 0; + Uint32 Format = 0; + Uint32 Stride = 0; + Uint64 Modifier = DrmFormatModLinear; + Uint64 Usage = 0; + Int PlaneCount = 1; + // Bytes per pixel of plane 0. Kept beside the layout because gbm_bo_map + // needs it to place a sub-rectangle, and because a format this library can + // allocate but cannot describe in bytes would be a format it must refuse. + Uint32 BytesPerPixel = 0; + + // Fds owned by this record. get_fd and get_fd_for_plane hand out dups so + // that a caller closing its fd can never invalidate the buffer. Index 0 is + // the only one a self-allocated buffer has: every plane of a linear image + // lives in that one dma-buf, and Fds[1..] stay -1 for it. An import with + // several descriptors fills several entries, and the destructor closes + // every entry that is not -1. + Int Fds[GbmMaxPlanes] = {-1, -1, -1, -1}; + Uint32 Strides[GbmMaxPlanes] = {0, 0, 0, 0}; + Uint32 Offsets[GbmMaxPlanes] = {0, 0, 0, 0}; + Uint64 Modifiers[GbmMaxPlanes] = {0, 0, 0, 0}; + + // The number of bytes the descriptors actually back, rounded up to the page + // size because that is what both backends allocate. It is the only length + // that is safe to mmap: a mapping longer than the object faults on the last + // page, and a shorter one would hide the caller's own stride arithmetic. + SizeT AllocationSize = 0; + + GbmBackend Backend = GbmBackend::None; + + // Set only for host frames: the slot index and sequence number that the + // release message has to mention so the phone can recycle the right slot. + Uint32 HostIndex = 0; + Uint32 HostSeq = 0; + Bool HostOwned = false; + + // How many maps are outstanding. The mapping itself is kept here so that + // unmap can find and tear down the right one. + Int MapRefCount = 0; + void* MapAddress = nullptr; + SizeT MapSize = 0; + + // Surfaces keep their buffers alive by count rather than by ownership, so + // the device knows when a bo is truly unreferenced. + Int RefCount = 1; + + // gbm_bo_set_user_data's pair. The destroy callback is invoked with the + // gbm_bo that is going away, once, from the destructor below. + void* UserData = nullptr; + void (*UserDataDestroy)(::gbm_bo*, void*) = nullptr; + + ::gbm_device* Owner = nullptr; + + // The record releases its own descriptors and its own mapping, so that no + // failure path anywhere in this library can leak either one - an allocation + // that fails half way through construction still cleans up when the record + // it was building is destroyed. + ~GbmBufferObject(); + }; + + // A real swapchain. The compositor asks for a buffer to render into, presents + // it, and later returns it; the three states below are exactly the states the + // GBM API can express. + struct GbmSurface + { + Uint32 Width = 0; + Uint32 Height = 0; + Uint32 Format = 0; + Uint32 Flags = 0; + + std::vector<::gbm_bo*> Buffers; + // Index of the buffer currently handed to the compositor as the front + // buffer, or -1 when nothing has been locked. + Int LockedIndex = -1; + // Indices of buffers the compositor has released and that may be handed + // out again, plus the one that is locked. + std::vector Free; + + ::gbm_device* Owner = nullptr; + + // Dropping the surface's references here is what keeps the creation path + // free of a second failure path: if the third swapchain buffer cannot be + // allocated, unwinding the half-built surface returns the two that exist. + ~GbmSurface(); + }; + + // The device ties one backend choice, one host channel and one mutex together. + // The single mutex is deliberate: GBM calls arrive from the compositor's + // render thread and from its main thread, and the state they touch (the bo + // table, the backends) is small enough that one lock is cheaper than several. + struct GbmDevice + { + // The fd this device was created with. It is a private dup taken at + // creation, never the caller's descriptor, so gbm_device_destroy can close + // it without ever closing an fd the caller still has open. -1 when the + // caller passed none and no render node could be opened: the self-allocated + // backend does not need a DRM node, so that is a working device and not an + // error. + Int DeviceFd = -1; + // The dma-heap node this device allocates from, or -1 when none could be + // opened, in which case every allocation falls back to a memfd. + Int HeapFd = -1; + String HeapPath; + + String SocketPath; + GbmBackend DefaultBackend = GbmBackend::DmaHeap; + Bool HostAvailable = false; + // The name gbm_device_get_backend_name reports. It names what this device + // actually does, because a compositor that logs it is asking exactly that. + String BackendName = "mobilegl"; + + std::mutex Mutex; + std::unordered_map<::gbm_bo*, GbmBufferObject*> Buffers; + std::unordered_map<::gbm_surface*, GbmSurface*> Surfaces; + + // The host channel is a separate translation unit so that the socket + // protocol never leaks into the allocation paths. This run implements only + // the self-allocated backend, so the pointer stays null and HostAvailable + // stays false; the member and the wire structs above are the hook. + class GbmHostChannel* Host = nullptr; + + // Closes the heap node and the private device dup, so that a device which + // fails to finish constructing still releases the descriptors it opened. + ~GbmDevice(); + }; + + // ---- format description ------------------------------------------------ + + // Fills planes[]/planeCount/totalBytes with the linear layout this library + // would allocate for format at width x height, and returns false for a format + // it cannot lay out. gbm_bo_create refuses a format this refuses, because a + // buffer whose plane layout was guessed is worse than a buffer that was not + // handed out at all. + Bool GbmDescribeFormat(Uint32 format, Uint32 width, Uint32 height, GbmPlaneLayout planes[GbmMaxPlanes], + Int& planeCount, SizeT& totalBytes) noexcept; + + // Whether this library can allocate the format at all. + Bool GbmFormatIsSupported(Uint32 format) noexcept; + + // Bytes per pixel of plane 0: what gbm_bo_map needs to place a sub-rectangle, + // and half of what gbm_bo_get_bpp reports. + Uint32 GbmFormatBytesPerPixel(Uint32 format) noexcept; + + // ---- records and the self-allocated backend ---------------------------- + + // Builds a device. The only helper here that is called without the mutex, + // because no other thread can see the device it is building. + ::gbm_device* GbmDeviceCreate(Int fd) noexcept; + + // Tears the device down: every remaining surface, every remaining buffer, the + // heap node, the private device fd, and the record itself. + void GbmDeviceDestroy(::gbm_device& device) noexcept; + + // Allocates a buffer from the device's heap node, or from a memfd when there + // is none, and records it in the device's table. Returns null with errno set. + ::gbm_bo* GbmBufferCreate(::gbm_device& device, Uint32 width, Uint32 height, Uint32 format, Uint64 usage, + const Uint64* modifiers, Uint32 modifierCount) noexcept; + + // Wraps descriptors the caller already owns. The descriptors are duped, so the + // caller keeps ownership of its own; type is GBM_BO_IMPORT_FD or + // GBM_BO_IMPORT_FD_MODIFIER. + ::gbm_bo* GbmBufferImport(::gbm_device& device, Uint32 type, const void* buffer, Uint64 usage) noexcept; + + // Drops one reference, destroying the buffer when it was the last one. + void GbmBufferRelease(::gbm_device& device, ::gbm_bo& bo) noexcept; + + // Destroys a buffer outright, whatever its reference count says. Only the + // device's own teardown uses this: by then no caller's reference is meaningful. + void GbmBufferForceDestroy(::gbm_device& device, ::gbm_bo& bo) noexcept; + + // Hands out a dup of the descriptor backing a plane, which the caller then owns. + // A plane this buffer does not have is refused with errno = EINVAL. + Int GbmBufferDupFd(::gbm_bo& bo, Int plane) noexcept; + + // Maps the whole buffer once and counts the call; strideOut receives the + // stride of plane 0. The rect (x, y) is applied to the returned address. + void* GbmBufferMap(::gbm_bo& bo, Uint32 x, Uint32 y, Uint32& strideOut) noexcept; + + // Drops one mapping; the last one unmaps. + void GbmBufferUnmap(::gbm_bo& bo, void* mapData) noexcept; + + // ---- swapchains -------------------------------------------------------- + + // Builds a surface and its swapchain up front, so that + // gbm_surface_has_free_buffers can answer truthfully from the first call. + ::gbm_surface* GbmSurfaceCreate(::gbm_device& device, Uint32 width, Uint32 height, Uint32 format, Uint64 usage, + const Uint64* modifiers, Uint32 modifierCount) noexcept; + + // Unregisters the surface and destroys it, returning the swapchain buffers it + // still owns to the device. + void GbmSurfaceDestroy(::gbm_surface& surface) noexcept; + + // Hands out the next free swapchain buffer with a reference of its own, or + // null with errno = EBUSY when the compositor still holds every one of them. + ::gbm_bo* GbmSurfaceLockFront(::gbm_surface& surface) noexcept; + + // Takes a locked buffer back, dropping the reference the lock handed out. + void GbmSurfaceReleaseBuffer(::gbm_surface& surface, ::gbm_bo& bo) noexcept; + + Bool GbmSurfaceHasFreeBuffers(::gbm_surface& surface) noexcept; +} // namespace MobileGL::MG_Gbm + +// The public opaque types are the internal records. Callers only ever see the +// pointers, so this costs nothing and removes a layer of casting. +struct gbm_device +{ + MobileGL::MG_Gbm::GbmDevice Impl; +}; + +struct gbm_bo +{ + MobileGL::MG_Gbm::GbmBufferObject Impl; +}; + +struct gbm_surface +{ + MobileGL::MG_Gbm::GbmSurface Impl; +}; + +// End of Source File Header