diff --git a/src/backends/native/meta-drm-buffer-import.c b/src/backends/native/meta-drm-buffer-import.c new file mode 100644 index 000000000..b9d0e21d2 --- /dev/null +++ b/src/backends/native/meta-drm-buffer-import.c @@ -0,0 +1,197 @@ +/* + * Copyright (C) 2011 Intel Corporation. + * Copyright (C) 2016,2017 Red Hat + * Copyright (C) 2018,2019 DisplayLink (UK) Ltd. + * Copyright (C) 2018 Canonical Ltd. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + */ + +#include "config.h" + +#include "backends/native/meta-drm-buffer-import.h" + +#include +#include +#include +#include + +#include "backends/native/meta-drm-buffer-gbm.h" +#include "backends/native/meta-kms-utils.h" +#include "backends/native/meta-renderer-native.h" + +#define INVALID_FB_ID 0U + +struct _MetaDrmBufferImport +{ + MetaDrmBuffer parent; + + MetaGpuKms *gpu_kms; + + MetaDrmBufferGbm *importee; + + uint32_t fb_id; +}; + +G_DEFINE_TYPE (MetaDrmBufferImport, meta_drm_buffer_import, + META_TYPE_DRM_BUFFER) + +static struct gbm_bo * +dmabuf_to_gbm_bo (struct gbm_device *importer, + int dmabuf_fd, + uint32_t width, + uint32_t height, + uint32_t stride, + uint32_t format) +{ + struct gbm_import_fd_data data = { + .fd = dmabuf_fd, + .width = width, + .height = height, + .stride = stride, + .format = format + }; + + return gbm_bo_import (importer, + GBM_BO_IMPORT_FD, + &data, + GBM_BO_USE_SCANOUT); +} + +static gboolean +import_gbm_buffer (MetaDrmBufferImport *buffer_import, + GError **error) +{ + MetaGpuKmsFBArgs fb_args = { 0, }; + struct gbm_bo *primary_bo; + struct gbm_device *importer; + struct gbm_bo *imported_bo; + int dmabuf_fd; + gboolean ret; + + g_assert (buffer_import->fb_id == INVALID_FB_ID); + + importer = meta_gbm_device_from_gpu (buffer_import->gpu_kms); + + primary_bo = meta_drm_buffer_gbm_get_bo (buffer_import->importee); + + dmabuf_fd = gbm_bo_get_fd (primary_bo); + if (dmabuf_fd == -1) + { + g_set_error (error, + G_IO_ERROR, + G_IO_ERROR_FAILED, + "getting dmabuf fd failed"); + return FALSE; + } + + fb_args.strides[0] = gbm_bo_get_stride (primary_bo); + fb_args.width = gbm_bo_get_width (primary_bo); + fb_args.height = gbm_bo_get_height (primary_bo); + fb_args.format = gbm_bo_get_format (primary_bo); + + imported_bo = dmabuf_to_gbm_bo (importer, + dmabuf_fd, + fb_args.width, + fb_args.height, + fb_args.strides[0], + fb_args.format); + if (!imported_bo) + { + g_set_error (error, + G_IO_ERROR, + G_IO_ERROR_FAILED, + "importing dmabuf fd failed"); + ret = FALSE; + goto out_close; + } + + fb_args.handles[0] = gbm_bo_get_handle (imported_bo).u32; + + ret = meta_gpu_kms_add_fb (buffer_import->gpu_kms, + FALSE /* use_modifiers */, + &fb_args, + &buffer_import->fb_id, + error); + + gbm_bo_destroy (imported_bo); + +out_close: + close (dmabuf_fd); + + return ret; +} + +MetaDrmBufferImport * +meta_drm_buffer_import_new (MetaGpuKms *gpu_kms, + MetaDrmBufferGbm *buffer_gbm, + GError **error) +{ + MetaDrmBufferImport *buffer_import; + + buffer_import = g_object_new (META_TYPE_DRM_BUFFER_IMPORT, NULL); + buffer_import->gpu_kms = gpu_kms; + g_set_object (&buffer_import->importee, buffer_gbm); + + if (!import_gbm_buffer (buffer_import, error)) + { + g_object_unref (buffer_import); + return NULL; + } + + return buffer_import; +} + +static uint32_t +meta_drm_buffer_import_get_fb_id (MetaDrmBuffer *buffer) +{ + return META_DRM_BUFFER_IMPORT (buffer)->fb_id; +} + +static void +meta_drm_buffer_import_finalize (GObject *object) +{ + MetaDrmBufferImport *buffer_import = META_DRM_BUFFER_IMPORT (object); + + if (buffer_import->fb_id != INVALID_FB_ID) + { + int kms_fd; + + kms_fd = meta_gpu_kms_get_fd (buffer_import->gpu_kms); + drmModeRmFB (kms_fd, buffer_import->fb_id); + } + + g_clear_object (&buffer_import->importee); + + G_OBJECT_CLASS (meta_drm_buffer_import_parent_class)->finalize (object); +} + +static void +meta_drm_buffer_import_init (MetaDrmBufferImport *buffer_import) +{ +} + +static void +meta_drm_buffer_import_class_init (MetaDrmBufferImportClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + MetaDrmBufferClass *buffer_class = META_DRM_BUFFER_CLASS (klass); + + object_class->finalize = meta_drm_buffer_import_finalize; + + buffer_class->get_fb_id = meta_drm_buffer_import_get_fb_id; +} diff --git a/src/backends/native/meta-drm-buffer-import.h b/src/backends/native/meta-drm-buffer-import.h new file mode 100644 index 000000000..2c0962e5f --- /dev/null +++ b/src/backends/native/meta-drm-buffer-import.h @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2018 Canonical Ltd. + * Copyright (C) 2019 Red Hat Inc. + * Copyright (C) 2019 DisplayLink (UK) Ltd. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + */ + +#ifndef META_DRM_BUFFER_IMPORT_H +#define META_DRM_BUFFER_IMPORT_H + +#include + +#include "backends/native/meta-drm-buffer.h" +#include "backends/native/meta-drm-buffer-gbm.h" +#include "backends/native/meta-gpu-kms.h" + +#define META_TYPE_DRM_BUFFER_IMPORT (meta_drm_buffer_import_get_type ()) +G_DECLARE_FINAL_TYPE (MetaDrmBufferImport, + meta_drm_buffer_import, + META, DRM_BUFFER_IMPORT, + MetaDrmBuffer) + +/* + * MetaDrmBufferImport is a buffer that refers to the storage of a + * MetaDrmBufferGbm buffer on another MetaGpuKms. + * + * When creating an imported buffer, the given GBM buffer is exported + * as a dma_buf and then imported to the given MetaGpuKms. A reference + * is kept to the GBM buffer so that it won't disappear while the + * imported buffer exists. + * + * The import has a high chance of failing under normal operating + * conditions and needs to be handled with fallbacks to something else. + */ +MetaDrmBufferImport * meta_drm_buffer_import_new (MetaGpuKms *gpu_kms, + MetaDrmBufferGbm *buffer_gbm, + GError **error); + +#endif /* META_DRM_BUFFER_IMPORT_H */ diff --git a/src/backends/native/meta-renderer-native.c b/src/backends/native/meta-renderer-native.c index c7ba5b0ea..6622bb4cb 100644 --- a/src/backends/native/meta-renderer-native.c +++ b/src/backends/native/meta-renderer-native.c @@ -60,6 +60,7 @@ #include "backends/native/meta-crtc-kms.h" #include "backends/native/meta-drm-buffer-dumb.h" #include "backends/native/meta-drm-buffer-gbm.h" +#include "backends/native/meta-drm-buffer-import.h" #include "backends/native/meta-drm-buffer.h" #include "backends/native/meta-gpu-kms.h" #include "backends/native/meta-kms-update.h" @@ -95,6 +96,8 @@ static GParamSpec *obj_props[PROP_LAST]; typedef enum _MetaSharedFramebufferCopyMode { + /* Zero-copy: primary GPU exports, secondary GPU imports as KMS FB */ + META_SHARED_FRAMEBUFFER_COPY_MODE_ZERO, /* the secondary GPU will make the copy */ META_SHARED_FRAMEBUFFER_COPY_MODE_SECONDARY_GPU, /* @@ -150,6 +153,16 @@ typedef struct _MetaDumbBuffer int dmabuf_fd; } MetaDumbBuffer; +typedef enum _MetaSharedFramebufferImportStatus +{ + /* Not tried importing yet. */ + META_SHARED_FRAMEBUFFER_IMPORT_STATUS_NONE, + /* Tried before and failed. */ + META_SHARED_FRAMEBUFFER_IMPORT_STATUS_FAILED, + /* Tried before and succeeded. */ + META_SHARED_FRAMEBUFFER_IMPORT_STATUS_OK +} MetaSharedFramebufferImportStatus; + typedef struct _MetaOnscreenNativeSecondaryGpuState { MetaGpuKms *gpu_kms; @@ -172,6 +185,7 @@ typedef struct _MetaOnscreenNativeSecondaryGpuState gboolean noted_primary_gpu_copy_ok; gboolean noted_primary_gpu_copy_failed; + MetaSharedFramebufferImportStatus import_status; } MetaOnscreenNativeSecondaryGpuState; typedef struct _MetaOnscreenNative @@ -867,6 +881,13 @@ init_secondary_gpu_state_cpu_copy_mode (MetaRendererNative *renderer_nat } } + /* + * This function initializes everything needed for + * META_SHARED_FRAMEBUFFER_COPY_MODE_ZERO as well. + */ + secondary_gpu_state->import_status = + META_SHARED_FRAMEBUFFER_IMPORT_STATUS_NONE; + g_hash_table_insert (onscreen_native->secondary_gpu_states, gpu_kms, secondary_gpu_state); @@ -894,6 +915,13 @@ init_secondary_gpu_state (MetaRendererNative *renderer_native, error)) return FALSE; break; + case META_SHARED_FRAMEBUFFER_COPY_MODE_ZERO: + /* + * Initialize also the primary copy mode, so that if zero-copy + * path fails, which is quite likely, we can simply continue + * with the primary copy path on the very first frame. + */ + G_GNUC_FALLTHROUGH; case META_SHARED_FRAMEBUFFER_COPY_MODE_PRIMARY: if (!init_secondary_gpu_state_cpu_copy_mode (renderer_native, onscreen, @@ -1741,6 +1769,73 @@ wait_for_pending_flips (CoglOnscreen *onscreen) } } +static gboolean +import_shared_framebuffer (CoglOnscreen *onscreen, + MetaOnscreenNativeSecondaryGpuState *secondary_gpu_state) +{ + CoglOnscreenEGL *onscreen_egl = onscreen->winsys; + MetaOnscreenNative *onscreen_native = onscreen_egl->platform; + MetaDrmBufferGbm *buffer_gbm; + MetaDrmBufferImport *buffer_import; + g_autoptr (GError) error = NULL; + + buffer_gbm = META_DRM_BUFFER_GBM (onscreen_native->gbm.next_fb); + + buffer_import = meta_drm_buffer_import_new (secondary_gpu_state->gpu_kms, + buffer_gbm, + &error); + if (!buffer_import) + { + g_debug ("Zero-copy disabled for %s, meta_drm_buffer_import_new failed: %s", + meta_gpu_kms_get_file_path (secondary_gpu_state->gpu_kms), + error->message); + + g_warn_if_fail (secondary_gpu_state->import_status == + META_SHARED_FRAMEBUFFER_IMPORT_STATUS_NONE); + + /* + * Fall back. If META_SHARED_FRAMEBUFFER_IMPORT_STATUS_NONE is + * in effect, we have COPY_MODE_PRIMARY prepared already, so we + * simply retry with that path. Import status cannot be FAILED, + * because we should not retry if failed once. + * + * If import status is OK, that is unexpected and we do not + * have the fallback path prepared which means this output cannot + * work anymore. + */ + secondary_gpu_state->renderer_gpu_data->secondary.copy_mode = + META_SHARED_FRAMEBUFFER_COPY_MODE_PRIMARY; + + secondary_gpu_state->import_status = + META_SHARED_FRAMEBUFFER_IMPORT_STATUS_FAILED; + return FALSE; + } + + /* + * next_fb may already contain a fallback buffer, so clear it only + * when we are sure to succeed. + */ + g_clear_object (&secondary_gpu_state->gbm.next_fb); + secondary_gpu_state->gbm.next_fb = META_DRM_BUFFER (buffer_import); + + if (secondary_gpu_state->import_status == + META_SHARED_FRAMEBUFFER_IMPORT_STATUS_NONE) + { + /* + * Clean up the cpu-copy part of + * init_secondary_gpu_state_cpu_copy_mode () + */ + secondary_gpu_release_dumb (secondary_gpu_state); + + g_debug ("Using zero-copy for %s succeeded once.", + meta_gpu_kms_get_file_path (secondary_gpu_state->gpu_kms)); + } + + secondary_gpu_state->import_status = + META_SHARED_FRAMEBUFFER_IMPORT_STATUS_OK; + return TRUE; +} + static void copy_shared_framebuffer_gpu (CoglOnscreen *onscreen, MetaOnscreenNativeSecondaryGpuState *secondary_gpu_state, @@ -2083,6 +2178,13 @@ update_secondary_gpu_state_pre_swap_buffers (CoglOnscreen *onscreen) case META_SHARED_FRAMEBUFFER_COPY_MODE_SECONDARY_GPU: /* Done after eglSwapBuffers. */ break; + case META_SHARED_FRAMEBUFFER_COPY_MODE_ZERO: + /* Done after eglSwapBuffers. */ + if (secondary_gpu_state->import_status == + META_SHARED_FRAMEBUFFER_IMPORT_STATUS_OK) + break; + /* prepare fallback */ + G_GNUC_FALLTHROUGH; case META_SHARED_FRAMEBUFFER_COPY_MODE_PRIMARY: if (!copy_shared_framebuffer_primary_gpu (onscreen, secondary_gpu_state)) @@ -2132,8 +2234,14 @@ update_secondary_gpu_state_post_swap_buffers (CoglOnscreen *onscreen, renderer_gpu_data = meta_renderer_native_get_gpu_data (renderer_native, secondary_gpu_state->gpu_kms); +retry: switch (renderer_gpu_data->secondary.copy_mode) { + case META_SHARED_FRAMEBUFFER_COPY_MODE_ZERO: + if (!import_shared_framebuffer (onscreen, + secondary_gpu_state)) + goto retry; + break; case META_SHARED_FRAMEBUFFER_COPY_MODE_SECONDARY_GPU: copy_shared_framebuffer_gpu (onscreen, secondary_gpu_state, @@ -3571,7 +3679,10 @@ static void init_secondary_gpu_data_cpu (MetaRendererNativeGpuData *renderer_gpu_data) { renderer_gpu_data->secondary.is_hardware_rendering = FALSE; - renderer_gpu_data->secondary.copy_mode = META_SHARED_FRAMEBUFFER_COPY_MODE_PRIMARY; + + /* First try ZERO, it automatically falls back to PRIMARY as needed */ + renderer_gpu_data->secondary.copy_mode = + META_SHARED_FRAMEBUFFER_COPY_MODE_ZERO; } static void diff --git a/src/meson.build b/src/meson.build index 012d78310..1f9bab95f 100644 --- a/src/meson.build +++ b/src/meson.build @@ -609,6 +609,8 @@ if have_native_backend 'backends/native/meta-drm-buffer-dumb.h', 'backends/native/meta-drm-buffer-gbm.c', 'backends/native/meta-drm-buffer-gbm.h', + 'backends/native/meta-drm-buffer-import.c', + 'backends/native/meta-drm-buffer-import.h', 'backends/native/meta-drm-buffer.c', 'backends/native/meta-drm-buffer.h', 'backends/native/meta-event-native.c',