| // Copyright 2024 The Chromium Authors |
| // Copyright 2024 The ChromiumOS Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| // Fake GBM is a GBM "implementation" designed to run in a completely headless |
| // environment. This means that fake GBM does not need an actual GEM driver to |
| // operate, and instead uses simple tmpfiles to back its memory allocation. |
| // |
| // The motivation behind Fake GBM is that it was discovered that not all system |
| // GBM implementations behave the same, and this can sometimes cause problems |
| // when testing hardware accelerated video decoders on different systems, even |
| // when using the fake VA-API backend. |
| // |
| // Fake GBM should be ABI compatible with Minigbm. So usually the shared library |
| // built for Fake GBM will simply be DLL injected (using LD_PRELOAD generally) |
| // into the test process. |
| |
| #ifdef UNSAFE_BUFFERS_BUILD |
| // We need to conform to the GBM API, which unfortunately involves a lot of |
| // unsafe buffer access to maintain C99 compatibility. |
| #pragma allow_unsafe_buffers |
| #endif |
| |
| #include <errno.h> |
| #include <gbm.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <sys/mman.h> |
| #include <unistd.h> |
| |
| #include "base/logging.h" |
| |
| #define PAGE_SIZE 4096 |
| |
| #define ALIGN(x, y) (((x + y - 1) / y) * y) |
| |
| #define DRM_NODE_RENDER 2 |
| |
| #define GBM_EXPORT __attribute__((visibility("default"))) |
| |
| // This is an opaque type in GBM, so its definition does not really matter. |
| struct gbm_device { |
| uint8_t pad; |
| }; |
| |
| // This is technically not GBM, it's DRM, but it's necessary for some GBM and |
| // libva clients to be happy with our fake drivers. |
| extern "C" GBM_EXPORT int drmGetNodeTypeFromFd(int fd) { |
| return DRM_NODE_RENDER; |
| } |
| |
| struct gbm_bo { |
| struct gbm_import_fd_modifier_data meta; |
| }; |
| |
| struct gbm_bo_mapping { |
| void* addr; |
| size_t size; |
| }; |
| |
| uint32_t get_y_subsample(struct gbm_bo* bo, size_t plane) { |
| if (plane == 0) { |
| return 1; |
| } else { |
| return 2; |
| } |
| } |
| |
| uint32_t get_x_subsample(struct gbm_bo* bo, size_t plane) { |
| if (plane == 0) { |
| return 1; |
| } |
| |
| switch (bo->meta.format) { |
| case GBM_FORMAT_NV12: |
| case GBM_FORMAT_P010: |
| return 1; |
| case GBM_FORMAT_YUV420: |
| return 2; |
| default: |
| CHECK(false); |
| } |
| } |
| |
| uint32_t get_plane_min_size(struct gbm_bo* bo, size_t plane) { |
| return bo->meta.strides[plane] * |
| ALIGN(bo->meta.height, get_y_subsample(bo, plane)) / |
| get_y_subsample(bo, plane); |
| } |
| |
| extern "C" GBM_EXPORT struct gbm_device* gbm_create_device(int fd) { |
| return new struct gbm_device; |
| } |
| |
| extern "C" GBM_EXPORT void gbm_device_destroy(struct gbm_device* gbm) { |
| delete gbm; |
| } |
| |
| extern "C" GBM_EXPORT uint32_t gbm_bo_get_bpp(struct gbm_bo* bo) { |
| CHECK(bo); |
| |
| switch (bo->meta.format) { |
| case GBM_FORMAT_NV12: |
| case GBM_FORMAT_YUV420: |
| return 1; |
| case GBM_FORMAT_P010: |
| return 2; |
| default: |
| CHECK(false); |
| } |
| } |
| |
| extern "C" GBM_EXPORT int gbm_bo_get_plane_count(struct gbm_bo* bo) { |
| CHECK(bo); |
| |
| switch (bo->meta.format) { |
| case GBM_FORMAT_NV12: |
| case GBM_FORMAT_P010: |
| return 2; |
| case GBM_FORMAT_YUV420: |
| return 3; |
| default: |
| CHECK(false); |
| } |
| } |
| |
| extern "C" GBM_EXPORT struct gbm_bo* gbm_bo_create(struct gbm_device* gbm, |
| uint32_t width, |
| uint32_t height, |
| uint32_t format, |
| uint32_t flags) { |
| CHECK(format == GBM_FORMAT_NV12 || format == GBM_FORMAT_P010 || |
| format == GBM_FORMAT_YUV420); |
| |
| FILE* backing_file = tmpfile(); |
| CHECK(backing_file); |
| int fd = fileno(backing_file); |
| |
| struct gbm_bo* bo = new struct gbm_bo; |
| bo->meta.num_fds = 1; |
| bo->meta.fds[0] = fd; |
| bo->meta.width = width; |
| bo->meta.height = height; |
| bo->meta.format = format; |
| bo->meta.modifier = 0; |
| |
| uint32_t size = 0; |
| for (int i = 0; i < gbm_bo_get_plane_count(bo); i++) { |
| bo->meta.offsets[i] = size; |
| bo->meta.strides[i] = ALIGN(width, get_x_subsample(bo, i)) / |
| get_x_subsample(bo, i) * gbm_bo_get_bpp(bo); |
| size += ALIGN(get_plane_min_size(bo, i), PAGE_SIZE); |
| } |
| |
| CHECK(ftruncate(fd, size) == 0); |
| |
| return bo; |
| } |
| |
| extern "C" GBM_EXPORT void gbm_bo_destroy(struct gbm_bo* bo) { |
| CHECK(bo); |
| delete bo; |
| } |
| |
| extern "C" GBM_EXPORT void* gbm_bo_map2(struct gbm_bo* bo, |
| uint32_t x, |
| uint32_t y, |
| uint32_t width, |
| uint32_t height, |
| uint32_t transfer_flags, |
| uint32_t* stride, |
| void** map_data, |
| int plane) { |
| CHECK(bo); |
| CHECK(x + width <= bo->meta.width); |
| CHECK(y + height <= bo->meta.height); |
| CHECK(ALIGN(x, get_x_subsample(bo, plane)) == x); |
| CHECK(ALIGN(y, get_y_subsample(bo, plane)) == y); |
| CHECK(static_cast<int>(plane) < gbm_bo_get_plane_count(bo)); |
| |
| size_t size = ALIGN(get_plane_min_size(bo, plane), PAGE_SIZE); |
| void* addr = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, |
| bo->meta.fds[0], bo->meta.offsets[plane]); |
| CHECK(addr); |
| CHECK(addr != MAP_FAILED); |
| |
| struct gbm_bo_mapping** _map_data = |
| reinterpret_cast<struct gbm_bo_mapping**>(map_data); |
| *_map_data = new struct gbm_bo_mapping; |
| (*_map_data)->addr = addr; |
| (*_map_data)->size = size; |
| *stride = bo->meta.strides[plane]; |
| |
| size_t offset = y / get_y_subsample(bo, plane) * bo->meta.strides[plane] + |
| x / get_x_subsample(bo, plane) * gbm_bo_get_bpp(bo); |
| return reinterpret_cast<void*>(reinterpret_cast<uint8_t*>(addr) + offset); |
| } |
| |
| extern "C" GBM_EXPORT void gbm_bo_unmap(struct gbm_bo* bo, void* map_data) { |
| CHECK(bo); |
| |
| struct gbm_bo_mapping* _map_data = |
| reinterpret_cast<struct gbm_bo_mapping*>(map_data); |
| void* addr = _map_data->addr; |
| size_t size = _map_data->size; |
| munmap(addr, size); |
| delete _map_data; |
| } |
| |
| extern "C" GBM_EXPORT struct gbm_bo* gbm_bo_import(struct gbm_device* gbm, |
| uint32_t type, |
| void* buffer, |
| uint32_t usage) { |
| CHECK(buffer); |
| CHECK(type == GBM_BO_IMPORT_FD_MODIFIER || type == GBM_BO_IMPORT_FD_PLANAR); |
| |
| struct gbm_bo* bo = new struct gbm_bo; |
| bo->meta = *reinterpret_cast<struct gbm_import_fd_modifier_data*>(buffer); |
| |
| return bo; |
| } |
| |
| extern "C" GBM_EXPORT int gbm_bo_get_fd(struct gbm_bo* bo) { |
| CHECK(bo); |
| |
| return dup(bo->meta.fds[0]); |
| } |
| |
| extern "C" GBM_EXPORT int gbm_bo_get_fd_for_plane(struct gbm_bo* bo, |
| int plane) { |
| CHECK(bo); |
| CHECK(plane >= 0); |
| CHECK(static_cast<int>(plane) < gbm_bo_get_plane_count(bo)); |
| |
| return gbm_bo_get_fd(bo); |
| } |
| |
| extern "C" GBM_EXPORT uint64_t gbm_bo_get_modifier(struct gbm_bo* bo) { |
| return 0; |
| } |
| |
| extern "C" GBM_EXPORT uint32_t gbm_bo_get_format(struct gbm_bo* bo) { |
| CHECK(bo); |
| |
| return bo->meta.format; |
| } |
| |
| extern "C" GBM_EXPORT uint32_t gbm_bo_get_offset(struct gbm_bo* bo, |
| int plane) { |
| CHECK(bo); |
| CHECK(static_cast<int>(plane) < gbm_bo_get_plane_count(bo)); |
| |
| return bo->meta.offsets[plane]; |
| } |
| |
| extern "C" GBM_EXPORT uint32_t gbm_bo_get_stride_for_plane(struct gbm_bo* bo, |
| int plane) { |
| CHECK(bo); |
| CHECK(static_cast<int>(plane) < gbm_bo_get_plane_count(bo)); |
| |
| return bo->meta.strides[plane]; |
| } |
| |
| extern "C" GBM_EXPORT uint32_t gbm_bo_get_width(struct gbm_bo* bo) { |
| CHECK(bo); |
| |
| return bo->meta.width; |
| } |
| |
| extern "C" GBM_EXPORT uint32_t gbm_bo_get_height(struct gbm_bo* bo) { |
| CHECK(bo); |
| |
| return bo->meta.height; |
| } |