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