diff options
Diffstat (limited to 'src/backends/native/meta-onscreen-native.c')
-rw-r--r-- | src/backends/native/meta-onscreen-native.c | 2196 |
1 files changed, 2196 insertions, 0 deletions
diff --git a/src/backends/native/meta-onscreen-native.c b/src/backends/native/meta-onscreen-native.c new file mode 100644 index 000000000..1617668ce --- /dev/null +++ b/src/backends/native/meta-onscreen-native.c @@ -0,0 +1,2196 @@ +/* + * Copyright (C) 2011 Intel Corporation. + * Copyright (C) 2016-2020 Red Hat + * Copyright (c) 2018,2019 DisplayLink (UK) Ltd. + * + * 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 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. + * + */ + +#include "config.h" + +#include "backends/native/meta-onscreen-native.h" + +#include <drm_fourcc.h> + +#include "backends/meta-egl-ext.h" +#include "backends/native/meta-cogl-utils.h" +#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-kms-utils.h" +#include "backends/native/meta-kms.h" +#include "backends/native/meta-output-kms.h" +#include "backends/native/meta-renderer-native-gles3.h" +#include "backends/native/meta-renderer-native-private.h" + +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; + MetaRendererNativeGpuData *renderer_gpu_data; + + EGLSurface egl_surface; + + struct { + struct gbm_surface *surface; + MetaDrmBuffer *current_fb; + MetaDrmBuffer *next_fb; + } gbm; + + struct { + MetaDrmBufferDumb *current_dumb_fb; + MetaDrmBufferDumb *dumb_fbs[2]; + } cpu; + + gboolean noted_primary_gpu_copy_ok; + gboolean noted_primary_gpu_copy_failed; + MetaSharedFramebufferImportStatus import_status; +} MetaOnscreenNativeSecondaryGpuState; + +typedef struct _MetaOnscreenNative +{ + MetaRendererNative *renderer_native; + MetaGpuKms *render_gpu; + MetaOutput *output; + MetaCrtc *crtc; + + MetaOnscreenNativeSecondaryGpuState *secondary_gpu_state; + + struct { + struct gbm_surface *surface; + MetaDrmBuffer *current_fb; + MetaDrmBuffer *next_fb; + } gbm; + +#ifdef HAVE_EGL_DEVICE + struct { + EGLStreamKHR stream; + + MetaDrmBufferDumb *dumb_fb; + } egl; +#endif + + MetaRendererView *view; +} MetaOnscreenNative; + +static void +swap_secondary_drm_fb (CoglOnscreen *onscreen) +{ + CoglOnscreenEgl *onscreen_egl = cogl_onscreen_get_winsys (onscreen); + MetaOnscreenNative *onscreen_native = + cogl_onscreen_egl_get_platform (onscreen_egl); + MetaOnscreenNativeSecondaryGpuState *secondary_gpu_state; + + secondary_gpu_state = onscreen_native->secondary_gpu_state; + if (!secondary_gpu_state) + return; + + g_set_object (&secondary_gpu_state->gbm.current_fb, + secondary_gpu_state->gbm.next_fb); + g_clear_object (&secondary_gpu_state->gbm.next_fb); +} + +static void +free_current_secondary_bo (CoglOnscreen *onscreen) +{ + CoglOnscreenEgl *onscreen_egl = cogl_onscreen_get_winsys (onscreen); + MetaOnscreenNative *onscreen_native = + cogl_onscreen_egl_get_platform (onscreen_egl); + MetaOnscreenNativeSecondaryGpuState *secondary_gpu_state; + + secondary_gpu_state = onscreen_native->secondary_gpu_state; + if (!secondary_gpu_state) + return; + + g_clear_object (&secondary_gpu_state->gbm.current_fb); +} + +static void +free_current_bo (CoglOnscreen *onscreen) +{ + CoglOnscreenEgl *onscreen_egl = cogl_onscreen_get_winsys (onscreen); + MetaOnscreenNative *onscreen_native = + cogl_onscreen_egl_get_platform (onscreen_egl); + + g_clear_object (&onscreen_native->gbm.current_fb); + free_current_secondary_bo (onscreen); +} + +static void +meta_onscreen_native_swap_drm_fb (CoglOnscreen *onscreen) +{ + CoglOnscreenEgl *onscreen_egl = cogl_onscreen_get_winsys (onscreen); + MetaOnscreenNative *onscreen_native = + cogl_onscreen_egl_get_platform (onscreen_egl); + + if (!onscreen_native->gbm.next_fb) + return; + + free_current_bo (onscreen); + + g_set_object (&onscreen_native->gbm.current_fb, onscreen_native->gbm.next_fb); + g_clear_object (&onscreen_native->gbm.next_fb); + + swap_secondary_drm_fb (onscreen); +} + +static void +maybe_update_frame_info (MetaCrtc *crtc, + CoglFrameInfo *frame_info, + int64_t time_ns) +{ + const MetaCrtcConfig *crtc_config; + const MetaCrtcModeInfo *crtc_mode_info; + float refresh_rate; + + g_return_if_fail (crtc); + + crtc_config = meta_crtc_get_config (crtc); + if (!crtc_config) + return; + + crtc_mode_info = meta_crtc_mode_get_info (crtc_config->mode); + refresh_rate = crtc_mode_info->refresh_rate; + if (refresh_rate >= frame_info->refresh_rate) + { + frame_info->presentation_time = time_ns; + frame_info->refresh_rate = refresh_rate; + } +} + +static void +meta_onscreen_native_notify_frame_complete (CoglOnscreen *onscreen) +{ + CoglFrameInfo *info; + + info = cogl_onscreen_pop_head_frame_info (onscreen); + + g_assert (!cogl_onscreen_peek_head_frame_info (onscreen)); + + _cogl_onscreen_notify_frame_sync (onscreen, info); + _cogl_onscreen_notify_complete (onscreen, info); + cogl_object_unref (info); +} + +static void +notify_view_crtc_presented (MetaRendererView *view, + MetaKmsCrtc *kms_crtc, + int64_t time_ns) +{ + ClutterStageView *stage_view = CLUTTER_STAGE_VIEW (view); + CoglFramebuffer *framebuffer = + clutter_stage_view_get_onscreen (stage_view); + CoglOnscreen *onscreen = COGL_ONSCREEN (framebuffer); + CoglOnscreenEgl *onscreen_egl = cogl_onscreen_get_winsys (onscreen); + MetaOnscreenNative *onscreen_native = + cogl_onscreen_egl_get_platform (onscreen_egl); + MetaRendererNative *renderer_native = onscreen_native->renderer_native; + CoglFrameInfo *frame_info; + MetaCrtc *crtc; + MetaRendererNativeGpuData *renderer_gpu_data; + + frame_info = cogl_onscreen_peek_head_frame_info (onscreen); + + crtc = META_CRTC (meta_crtc_kms_from_kms_crtc (kms_crtc)); + maybe_update_frame_info (crtc, frame_info, time_ns); + + meta_onscreen_native_notify_frame_complete (onscreen); + + renderer_gpu_data = + meta_renderer_native_get_gpu_data (renderer_native, + onscreen_native->render_gpu); + switch (renderer_gpu_data->mode) + { + case META_RENDERER_NATIVE_MODE_GBM: + meta_onscreen_native_swap_drm_fb (onscreen); + break; +#ifdef HAVE_EGL_DEVICE + case META_RENDERER_NATIVE_MODE_EGL_DEVICE: + break; +#endif + } +} + +static int64_t +timeval_to_nanoseconds (const struct timeval *tv) +{ + int64_t usec = ((int64_t) tv->tv_sec) * G_USEC_PER_SEC + tv->tv_usec; + int64_t nsec = usec * 1000; + + return nsec; +} + +static void +page_flip_feedback_flipped (MetaKmsCrtc *kms_crtc, + unsigned int sequence, + unsigned int tv_sec, + unsigned int tv_usec, + gpointer user_data) +{ + MetaRendererView *view = user_data; + struct timeval page_flip_time; + + page_flip_time = (struct timeval) { + .tv_sec = tv_sec, + .tv_usec = tv_usec, + }; + + notify_view_crtc_presented (view, kms_crtc, + timeval_to_nanoseconds (&page_flip_time)); +} + +static void +page_flip_feedback_ready (MetaKmsCrtc *kms_crtc, + gpointer user_data) +{ + MetaRendererView *view = user_data; + CoglFramebuffer *framebuffer = + clutter_stage_view_get_onscreen (CLUTTER_STAGE_VIEW (view)); + CoglOnscreen *onscreen = COGL_ONSCREEN (framebuffer); + CoglFrameInfo *frame_info; + + frame_info = cogl_onscreen_peek_head_frame_info (onscreen); + frame_info->flags |= COGL_FRAME_INFO_FLAG_SYMBOLIC; + + meta_onscreen_native_notify_frame_complete (onscreen); +} + +static void +page_flip_feedback_mode_set_fallback (MetaKmsCrtc *kms_crtc, + gpointer user_data) +{ + MetaRendererView *view = user_data; + MetaCrtc *crtc; + MetaGpuKms *gpu_kms; + int64_t now_ns; + + /* + * We ended up not page flipping, thus we don't have a presentation time to + * use. Lets use the next best thing: the current time. + */ + + crtc = META_CRTC (meta_crtc_kms_from_kms_crtc (kms_crtc)); + gpu_kms = META_GPU_KMS (meta_crtc_get_gpu (crtc)); + now_ns = meta_gpu_kms_get_current_time_ns (gpu_kms); + + notify_view_crtc_presented (view, kms_crtc, now_ns); +} + +static void +page_flip_feedback_discarded (MetaKmsCrtc *kms_crtc, + gpointer user_data, + const GError *error) +{ + MetaRendererView *view = user_data; + MetaCrtc *crtc; + MetaGpuKms *gpu_kms; + int64_t now_ns; + + /* + * Page flipping failed, but we want to fail gracefully, so to avoid freezing + * the frame clack, pretend we flipped. + */ + + if (error && + !g_error_matches (error, + G_IO_ERROR, + G_IO_ERROR_PERMISSION_DENIED)) + g_warning ("Page flip discarded: %s", error->message); + + crtc = META_CRTC (meta_crtc_kms_from_kms_crtc (kms_crtc)); + gpu_kms = META_GPU_KMS (meta_crtc_get_gpu (crtc)); + now_ns = meta_gpu_kms_get_current_time_ns (gpu_kms); + + notify_view_crtc_presented (view, kms_crtc, now_ns); +} + +static const MetaKmsPageFlipListenerVtable page_flip_listener_vtable = { + .flipped = page_flip_feedback_flipped, + .ready = page_flip_feedback_ready, + .mode_set_fallback = page_flip_feedback_mode_set_fallback, + .discarded = page_flip_feedback_discarded, +}; + +static MetaEgl * +meta_onscreen_native_get_egl (MetaOnscreenNative *onscreen_native) +{ + MetaRendererNative *renderer_native = onscreen_native->renderer_native; + + return meta_renderer_native_get_egl (renderer_native); +} + +#ifdef HAVE_EGL_DEVICE +static int +custom_egl_stream_page_flip (gpointer custom_page_flip_data, + gpointer user_data) +{ + MetaOnscreenNative *onscreen_native = custom_page_flip_data; + MetaRendererView *view = user_data; + MetaEgl *egl = meta_onscreen_native_get_egl (onscreen_native); + MetaRendererNativeGpuData *renderer_gpu_data; + EGLDisplay *egl_display; + EGLAttrib *acquire_attribs; + g_autoptr (GError) error = NULL; + + acquire_attribs = (EGLAttrib[]) { + EGL_DRM_FLIP_EVENT_DATA_NV, + (EGLAttrib) view, + EGL_NONE + }; + + renderer_gpu_data = + meta_renderer_native_get_gpu_data (onscreen_native->renderer_native, + onscreen_native->render_gpu); + + egl_display = renderer_gpu_data->egl_display; + if (!meta_egl_stream_consumer_acquire_attrib (egl, + egl_display, + onscreen_native->egl.stream, + acquire_attribs, + &error)) + { + if (g_error_matches (error, META_EGL_ERROR, EGL_RESOURCE_BUSY_EXT)) + return -EBUSY; + else + return -EINVAL; + } + + return 0; +} +#endif /* HAVE_EGL_DEVICE */ + +void +meta_onscreen_native_dummy_power_save_page_flip (CoglOnscreen *onscreen) +{ + CoglFrameInfo *frame_info; + + meta_onscreen_native_swap_drm_fb (onscreen); + + frame_info = cogl_onscreen_peek_tail_frame_info (onscreen); + frame_info->flags |= COGL_FRAME_INFO_FLAG_SYMBOLIC; + meta_onscreen_native_notify_frame_complete (onscreen); +} + +static void +meta_onscreen_native_flip_crtc (CoglOnscreen *onscreen, + MetaRendererView *view, + MetaCrtc *crtc, + MetaKmsPageFlipListenerFlag flags) +{ + CoglOnscreenEgl *onscreen_egl = cogl_onscreen_get_winsys (onscreen); + MetaOnscreenNative *onscreen_native = + cogl_onscreen_egl_get_platform (onscreen_egl); + MetaRendererNative *renderer_native = onscreen_native->renderer_native; + MetaGpuKms *render_gpu = onscreen_native->render_gpu; + MetaCrtcKms *crtc_kms = META_CRTC_KMS (crtc); + MetaKmsCrtc *kms_crtc = meta_crtc_kms_get_kms_crtc (crtc_kms); + MetaRendererNativeGpuData *renderer_gpu_data; + MetaGpuKms *gpu_kms; + MetaKmsDevice *kms_device; + MetaKms *kms; + MetaKmsUpdate *kms_update; + MetaOnscreenNativeSecondaryGpuState *secondary_gpu_state = NULL; + MetaDrmBuffer *buffer; + + COGL_TRACE_BEGIN_SCOPED (MetaOnscreenNativeFlipCrtcs, + "Onscreen (flip CRTCs)"); + + gpu_kms = META_GPU_KMS (meta_crtc_get_gpu (crtc)); + kms_device = meta_gpu_kms_get_kms_device (gpu_kms); + kms = meta_kms_device_get_kms (kms_device); + kms_update = meta_kms_ensure_pending_update (kms, kms_device); + + g_assert (meta_gpu_kms_is_crtc_active (gpu_kms, crtc)); + + renderer_gpu_data = meta_renderer_native_get_gpu_data (renderer_native, + render_gpu); + switch (renderer_gpu_data->mode) + { + case META_RENDERER_NATIVE_MODE_GBM: + if (gpu_kms == render_gpu) + { + buffer = onscreen_native->gbm.next_fb; + } + else + { + secondary_gpu_state = onscreen_native->secondary_gpu_state; + buffer = secondary_gpu_state->gbm.next_fb; + } + + meta_crtc_kms_assign_primary_plane (crtc_kms, buffer, kms_update); + + break; +#ifdef HAVE_EGL_DEVICE + case META_RENDERER_NATIVE_MODE_EGL_DEVICE: + meta_kms_update_set_custom_page_flip (kms_update, + custom_egl_stream_page_flip, + onscreen_native); + break; +#endif + } + + meta_kms_update_add_page_flip_listener (kms_update, + kms_crtc, + &page_flip_listener_vtable, + flags, + g_object_ref (view), + g_object_unref); +} + +static void +meta_onscreen_native_set_crtc_mode (CoglOnscreen *onscreen, + MetaRendererNativeGpuData *renderer_gpu_data) +{ + CoglOnscreenEgl *onscreen_egl = cogl_onscreen_get_winsys (onscreen); + MetaOnscreenNative *onscreen_native = + cogl_onscreen_egl_get_platform (onscreen_egl); + MetaCrtcKms *crtc_kms = META_CRTC_KMS (onscreen_native->crtc); + MetaKmsCrtc *kms_crtc = meta_crtc_kms_get_kms_crtc (crtc_kms); + MetaKmsDevice *kms_device = meta_kms_crtc_get_device (kms_crtc); + MetaKms *kms = meta_kms_device_get_kms (kms_device); + MetaKmsUpdate *kms_update; + + COGL_TRACE_BEGIN_SCOPED (MetaOnscreenNativeSetCrtcModes, + "Onscreen (set CRTC modes)"); + + kms_update = meta_kms_ensure_pending_update (kms, kms_device); + + switch (renderer_gpu_data->mode) + { + case META_RENDERER_NATIVE_MODE_GBM: + break; +#ifdef HAVE_EGL_DEVICE + case META_RENDERER_NATIVE_MODE_EGL_DEVICE: + { + MetaDrmBuffer *buffer; + + buffer = META_DRM_BUFFER (onscreen_native->egl.dumb_fb); + meta_crtc_kms_assign_primary_plane (crtc_kms, buffer, kms_update); + break; + } +#endif + } + + meta_crtc_kms_set_mode (crtc_kms, kms_update); + meta_output_kms_set_underscan (META_OUTPUT_KMS (onscreen_native->output), + kms_update); +} + +static void +secondary_gpu_release_dumb (MetaOnscreenNativeSecondaryGpuState *secondary_gpu_state) +{ + unsigned i; + + for (i = 0; i < G_N_ELEMENTS (secondary_gpu_state->cpu.dumb_fbs); i++) + g_clear_object (&secondary_gpu_state->cpu.dumb_fbs[i]); +} + +static void +secondary_gpu_state_free (MetaOnscreenNativeSecondaryGpuState *secondary_gpu_state) +{ + MetaBackend *backend = meta_get_backend (); + MetaEgl *egl = meta_backend_get_egl (backend); + + if (secondary_gpu_state->egl_surface != EGL_NO_SURFACE) + { + MetaRendererNativeGpuData *renderer_gpu_data; + + renderer_gpu_data = secondary_gpu_state->renderer_gpu_data; + meta_egl_destroy_surface (egl, + renderer_gpu_data->egl_display, + secondary_gpu_state->egl_surface, + NULL); + } + + g_clear_object (&secondary_gpu_state->gbm.current_fb); + g_clear_object (&secondary_gpu_state->gbm.next_fb); + g_clear_pointer (&secondary_gpu_state->gbm.surface, gbm_surface_destroy); + + secondary_gpu_release_dumb (secondary_gpu_state); + + g_free (secondary_gpu_state); +} + +static gboolean +import_shared_framebuffer (CoglOnscreen *onscreen, + MetaOnscreenNativeSecondaryGpuState *secondary_gpu_state) +{ + CoglOnscreenEgl *onscreen_egl = cogl_onscreen_get_winsys (onscreen); + MetaOnscreenNative *onscreen_native = + cogl_onscreen_egl_get_platform (onscreen_egl); + MetaGpuKms *gpu_kms; + MetaKmsDevice *kms_device; + struct gbm_device *gbm_device; + MetaDrmBufferGbm *buffer_gbm; + MetaDrmBufferImport *buffer_import; + g_autoptr (GError) error = NULL; + + buffer_gbm = META_DRM_BUFFER_GBM (onscreen_native->gbm.next_fb); + + gpu_kms = secondary_gpu_state->gpu_kms; + kms_device = meta_gpu_kms_get_kms_device (gpu_kms); + gbm_device = meta_gbm_device_from_gpu (gpu_kms); + buffer_import = meta_drm_buffer_import_new (kms_device, + gbm_device, + 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, + MetaRendererNativeGpuData *renderer_gpu_data, + gboolean *egl_context_changed) +{ + CoglOnscreenEgl *onscreen_egl = cogl_onscreen_get_winsys (onscreen); + MetaOnscreenNative *onscreen_native = + cogl_onscreen_egl_get_platform (onscreen_egl); + MetaRendererNative *renderer_native = renderer_gpu_data->renderer_native; + MetaEgl *egl = meta_renderer_native_get_egl (renderer_native); + MetaGles3 *gles3 = meta_renderer_native_get_gles3 (renderer_native); + GError *error = NULL; + gboolean use_modifiers; + MetaKmsDevice *kms_device; + MetaDrmBufferGbm *buffer_gbm; + struct gbm_bo *bo; + + COGL_TRACE_BEGIN_SCOPED (CopySharedFramebufferSecondaryGpu, + "FB Copy (secondary GPU)"); + + g_warn_if_fail (secondary_gpu_state->gbm.next_fb == NULL); + g_clear_object (&secondary_gpu_state->gbm.next_fb); + + if (!meta_egl_make_current (egl, + renderer_gpu_data->egl_display, + secondary_gpu_state->egl_surface, + secondary_gpu_state->egl_surface, + renderer_gpu_data->secondary.egl_context, + &error)) + { + g_warning ("Failed to make current: %s", error->message); + g_error_free (error); + return; + } + + *egl_context_changed = TRUE; + + + buffer_gbm = META_DRM_BUFFER_GBM (onscreen_native->gbm.next_fb); + bo = meta_drm_buffer_gbm_get_bo (buffer_gbm); + if (!meta_renderer_native_gles3_blit_shared_bo (egl, + gles3, + renderer_gpu_data->egl_display, + renderer_gpu_data->secondary.egl_context, + secondary_gpu_state->egl_surface, + bo, + &error)) + { + g_warning ("Failed to blit shared framebuffer: %s", error->message); + g_error_free (error); + return; + } + + if (!meta_egl_swap_buffers (egl, + renderer_gpu_data->egl_display, + secondary_gpu_state->egl_surface, + &error)) + { + g_warning ("Failed to swap buffers: %s", error->message); + g_error_free (error); + return; + } + + use_modifiers = meta_renderer_native_use_modifiers (renderer_native); + kms_device = meta_gpu_kms_get_kms_device (secondary_gpu_state->gpu_kms); + buffer_gbm = + meta_drm_buffer_gbm_new_lock_front (kms_device, + secondary_gpu_state->gbm.surface, + use_modifiers, + &error); + if (!buffer_gbm) + { + g_warning ("meta_drm_buffer_gbm_new_lock_front failed: %s", + error->message); + g_error_free (error); + return; + } + + secondary_gpu_state->gbm.next_fb = META_DRM_BUFFER (buffer_gbm); +} + +static MetaDrmBufferDumb * +secondary_gpu_get_next_dumb_buffer (MetaOnscreenNativeSecondaryGpuState *secondary_gpu_state) +{ + MetaDrmBufferDumb *current_dumb_fb; + + current_dumb_fb = secondary_gpu_state->cpu.current_dumb_fb; + if (current_dumb_fb == secondary_gpu_state->cpu.dumb_fbs[0]) + return secondary_gpu_state->cpu.dumb_fbs[1]; + else + return secondary_gpu_state->cpu.dumb_fbs[0]; +} + +static gboolean +copy_shared_framebuffer_primary_gpu (CoglOnscreen *onscreen, + MetaOnscreenNativeSecondaryGpuState *secondary_gpu_state) +{ + CoglFramebuffer *framebuffer = COGL_FRAMEBUFFER (onscreen); + CoglOnscreenEgl *onscreen_egl = cogl_onscreen_get_winsys (onscreen); + MetaOnscreenNative *onscreen_native = + cogl_onscreen_egl_get_platform (onscreen_egl); + MetaRendererNative *renderer_native = onscreen_native->renderer_native; + MetaGpuKms *primary_gpu; + MetaRendererNativeGpuData *primary_gpu_data; + MetaDrmBufferDumb *buffer_dumb; + MetaDrmBuffer *buffer; + int width, height, stride; + uint32_t drm_format; + CoglFramebuffer *dmabuf_fb; + int dmabuf_fd; + g_autoptr (GError) error = NULL; + CoglPixelFormat cogl_format; + int ret; + + COGL_TRACE_BEGIN_SCOPED (CopySharedFramebufferPrimaryGpu, + "FB Copy (primary GPU)"); + + primary_gpu = meta_renderer_native_get_primary_gpu (renderer_native); + primary_gpu_data = + meta_renderer_native_get_gpu_data (renderer_native, primary_gpu); + if (!primary_gpu_data->secondary.has_EGL_EXT_image_dma_buf_import_modifiers) + return FALSE; + + buffer_dumb = secondary_gpu_get_next_dumb_buffer (secondary_gpu_state); + buffer = META_DRM_BUFFER (buffer_dumb); + + width = meta_drm_buffer_get_width (buffer); + height = meta_drm_buffer_get_height (buffer); + stride = meta_drm_buffer_get_stride (buffer); + drm_format = meta_drm_buffer_get_format (buffer); + + g_assert (cogl_framebuffer_get_width (framebuffer) == width); + g_assert (cogl_framebuffer_get_height (framebuffer) == height); + + ret = meta_cogl_pixel_format_from_drm_format (drm_format, + &cogl_format, + NULL); + g_assert (ret); + + dmabuf_fd = meta_drm_buffer_dumb_ensure_dmabuf_fd (buffer_dumb, &error); + if (!dmabuf_fd) + { + g_debug ("Failed to create DMA buffer: %s", error->message); + return FALSE; + } + + dmabuf_fb = + meta_renderer_native_create_dma_buf_framebuffer (renderer_native, + dmabuf_fd, + width, + height, + stride, + 0, DRM_FORMAT_MOD_LINEAR, + drm_format, + &error); + + if (error) + { + g_debug ("%s: Failed to blit DMA buffer image: %s", + G_STRFUNC, error->message); + return FALSE; + } + + if (!cogl_blit_framebuffer (framebuffer, COGL_FRAMEBUFFER (dmabuf_fb), + 0, 0, 0, 0, + width, height, + &error)) + { + g_object_unref (dmabuf_fb); + return FALSE; + } + + g_object_unref (dmabuf_fb); + + g_clear_object (&secondary_gpu_state->gbm.next_fb); + secondary_gpu_state->gbm.next_fb = buffer; + secondary_gpu_state->cpu.current_dumb_fb = buffer_dumb; + + return TRUE; +} + +static void +copy_shared_framebuffer_cpu (CoglOnscreen *onscreen, + MetaOnscreenNativeSecondaryGpuState *secondary_gpu_state, + MetaRendererNativeGpuData *renderer_gpu_data) +{ + CoglFramebuffer *framebuffer = COGL_FRAMEBUFFER (onscreen); + CoglContext *cogl_context = cogl_framebuffer_get_context (framebuffer); + MetaDrmBufferDumb *buffer_dumb; + MetaDrmBuffer *buffer; + int width, height, stride; + uint32_t drm_format; + void *buffer_data; + CoglBitmap *dumb_bitmap; + CoglPixelFormat cogl_format; + gboolean ret; + + COGL_TRACE_BEGIN_SCOPED (CopySharedFramebufferCpu, + "FB Copy (CPU)"); + + buffer_dumb = secondary_gpu_get_next_dumb_buffer (secondary_gpu_state); + buffer = META_DRM_BUFFER (buffer_dumb); + + width = meta_drm_buffer_get_width (buffer); + height = meta_drm_buffer_get_height (buffer); + stride = meta_drm_buffer_get_stride (buffer); + drm_format = meta_drm_buffer_get_format (buffer); + buffer_data = meta_drm_buffer_dumb_get_data (buffer_dumb); + + g_assert (cogl_framebuffer_get_width (framebuffer) == width); + g_assert (cogl_framebuffer_get_height (framebuffer) == height); + + ret = meta_cogl_pixel_format_from_drm_format (drm_format, + &cogl_format, + NULL); + g_assert (ret); + + dumb_bitmap = cogl_bitmap_new_for_data (cogl_context, + width, + height, + cogl_format, + stride, + buffer_data); + + if (!cogl_framebuffer_read_pixels_into_bitmap (framebuffer, + 0 /* x */, + 0 /* y */, + COGL_READ_PIXELS_COLOR_BUFFER, + dumb_bitmap)) + g_warning ("Failed to CPU-copy to a secondary GPU output"); + + cogl_object_unref (dumb_bitmap); + + g_clear_object (&secondary_gpu_state->gbm.next_fb); + secondary_gpu_state->gbm.next_fb = buffer; + secondary_gpu_state->cpu.current_dumb_fb = buffer_dumb; +} + +static void +update_secondary_gpu_state_pre_swap_buffers (CoglOnscreen *onscreen) +{ + CoglOnscreenEgl *onscreen_egl = cogl_onscreen_get_winsys (onscreen); + MetaOnscreenNative *onscreen_native = + cogl_onscreen_egl_get_platform (onscreen_egl); + MetaOnscreenNativeSecondaryGpuState *secondary_gpu_state; + + COGL_TRACE_BEGIN_SCOPED (MetaRendererNativeGpuStatePreSwapBuffers, + "Onscreen (secondary gpu pre-swap-buffers)"); + + secondary_gpu_state = onscreen_native->secondary_gpu_state; + if (secondary_gpu_state) + { + MetaRendererNativeGpuData *renderer_gpu_data; + + renderer_gpu_data = secondary_gpu_state->renderer_gpu_data; + switch (renderer_gpu_data->secondary.copy_mode) + { + 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)) + { + if (!secondary_gpu_state->noted_primary_gpu_copy_failed) + { + g_debug ("Using primary GPU to copy for %s failed once.", + meta_gpu_kms_get_file_path (secondary_gpu_state->gpu_kms)); + secondary_gpu_state->noted_primary_gpu_copy_failed = TRUE; + } + + copy_shared_framebuffer_cpu (onscreen, + secondary_gpu_state, + renderer_gpu_data); + } + else if (!secondary_gpu_state->noted_primary_gpu_copy_ok) + { + g_debug ("Using primary GPU to copy for %s succeeded once.", + meta_gpu_kms_get_file_path (secondary_gpu_state->gpu_kms)); + secondary_gpu_state->noted_primary_gpu_copy_ok = TRUE; + } + break; + } + } +} + +static void +update_secondary_gpu_state_post_swap_buffers (CoglOnscreen *onscreen, + gboolean *egl_context_changed) +{ + CoglOnscreenEgl *onscreen_egl = cogl_onscreen_get_winsys (onscreen); + MetaOnscreenNative *onscreen_native = + cogl_onscreen_egl_get_platform (onscreen_egl); + MetaRendererNative *renderer_native = onscreen_native->renderer_native; + MetaOnscreenNativeSecondaryGpuState *secondary_gpu_state; + + COGL_TRACE_BEGIN_SCOPED (MetaRendererNativeGpuStatePostSwapBuffers, + "Onscreen (secondary gpu post-swap-buffers)"); + + secondary_gpu_state = onscreen_native->secondary_gpu_state; + if (secondary_gpu_state) + { + MetaRendererNativeGpuData *renderer_gpu_data; + + 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, + renderer_gpu_data, + egl_context_changed); + break; + case META_SHARED_FRAMEBUFFER_COPY_MODE_PRIMARY: + /* Done before eglSwapBuffers. */ + break; + } + } +} + +static void +ensure_crtc_modes (CoglOnscreen *onscreen) +{ + CoglOnscreenEgl *onscreen_egl = cogl_onscreen_get_winsys (onscreen); + MetaOnscreenNative *onscreen_native = + cogl_onscreen_egl_get_platform (onscreen_egl); + CoglFramebuffer *framebuffer = COGL_FRAMEBUFFER (onscreen); + CoglContext *cogl_context = cogl_framebuffer_get_context (framebuffer); + CoglRenderer *cogl_renderer = cogl_context->display->renderer; + CoglRendererEGL *cogl_renderer_egl = cogl_renderer->winsys; + MetaRendererNativeGpuData *renderer_gpu_data = cogl_renderer_egl->platform; + MetaRendererNative *renderer_native = renderer_gpu_data->renderer_native; + + if (meta_renderer_native_pop_pending_mode_set (renderer_native, + onscreen_native->view)) + meta_onscreen_native_set_crtc_mode (onscreen, renderer_gpu_data); +} + +void +meta_onscreen_native_swap_buffers_with_damage (CoglOnscreen *onscreen, + const int *rectangles, + int n_rectangles, + CoglFrameInfo *frame_info, + gpointer user_data) +{ + CoglFramebuffer *framebuffer = COGL_FRAMEBUFFER (onscreen); + CoglContext *cogl_context = cogl_framebuffer_get_context (framebuffer); + CoglDisplay *cogl_display = cogl_context_get_display (cogl_context); + CoglRenderer *cogl_renderer = cogl_context->display->renderer; + CoglRendererEGL *cogl_renderer_egl = cogl_renderer->winsys; + MetaRendererNativeGpuData *renderer_gpu_data = cogl_renderer_egl->platform; + MetaRendererNative *renderer_native = renderer_gpu_data->renderer_native; + MetaRenderer *renderer = META_RENDERER (renderer_native); + MetaBackend *backend = meta_renderer_get_backend (renderer); + MetaBackendNative *backend_native = META_BACKEND_NATIVE (backend); + MetaMonitorManager *monitor_manager = + meta_backend_get_monitor_manager (backend); + MetaKms *kms = meta_backend_native_get_kms (backend_native); + CoglOnscreenEgl *onscreen_egl = cogl_onscreen_get_winsys (onscreen); + MetaOnscreenNative *onscreen_native = + cogl_onscreen_egl_get_platform (onscreen_egl); + MetaGpuKms *render_gpu = onscreen_native->render_gpu; + MetaKmsDevice *render_kms_device = meta_gpu_kms_get_kms_device (render_gpu); + ClutterFrame *frame = user_data; + const CoglWinsysVtable *parent_vtable; + gboolean egl_context_changed = FALSE; + gboolean use_modifiers; + MetaPowerSave power_save_mode; + g_autoptr (GError) error = NULL; + MetaDrmBufferGbm *buffer_gbm; + MetaKmsCrtc *kms_crtc; + MetaKmsDevice *kms_device; + MetaKmsUpdateFlag flags; + g_autoptr (MetaKmsFeedback) kms_feedback = NULL; + const GError *feedback_error; + + COGL_TRACE_BEGIN_SCOPED (MetaRendererNativeSwapBuffers, + "Onscreen (swap-buffers)"); + + update_secondary_gpu_state_pre_swap_buffers (onscreen); + + parent_vtable = meta_get_renderer_native_parent_vtable (); + parent_vtable->onscreen_swap_buffers_with_damage (onscreen, + rectangles, + n_rectangles, + frame_info, + user_data); + + renderer_gpu_data = meta_renderer_native_get_gpu_data (renderer_native, + render_gpu); + switch (renderer_gpu_data->mode) + { + case META_RENDERER_NATIVE_MODE_GBM: + g_warn_if_fail (onscreen_native->gbm.next_fb == NULL); + g_clear_object (&onscreen_native->gbm.next_fb); + + use_modifiers = meta_renderer_native_use_modifiers (renderer_native); + buffer_gbm = + meta_drm_buffer_gbm_new_lock_front (render_kms_device, + onscreen_native->gbm.surface, + use_modifiers, + &error); + if (!buffer_gbm) + { + g_warning ("meta_drm_buffer_gbm_new_lock_front failed: %s", + error->message); + return; + } + + onscreen_native->gbm.next_fb = META_DRM_BUFFER (buffer_gbm); + + break; +#ifdef HAVE_EGL_DEVICE + case META_RENDERER_NATIVE_MODE_EGL_DEVICE: + break; +#endif + } + + update_secondary_gpu_state_post_swap_buffers (onscreen, &egl_context_changed); + + power_save_mode = meta_monitor_manager_get_power_save_mode (monitor_manager); + if (power_save_mode == META_POWER_SAVE_ON) + { + ensure_crtc_modes (onscreen); + meta_onscreen_native_flip_crtc (onscreen, + onscreen_native->view, + onscreen_native->crtc, + META_KMS_PAGE_FLIP_LISTENER_FLAG_NONE); + } + else + { + meta_renderer_native_queue_power_save_page_flip (renderer_native, + onscreen); + clutter_frame_set_result (frame, + CLUTTER_FRAME_RESULT_PENDING_PRESENTED); + return; + } + + /* + * If we changed EGL context, cogl will have the wrong idea about what is + * current, making it fail to set it when it needs to. Avoid that by making + * EGL_NO_CONTEXT current now, making cogl eventually set the correct + * context. + */ + if (egl_context_changed) + _cogl_winsys_egl_ensure_current (cogl_display); + + COGL_TRACE_BEGIN_SCOPED (MetaRendererNativePostKmsUpdate, + "Onscreen (post pending update)"); + kms_crtc = meta_crtc_kms_get_kms_crtc (META_CRTC_KMS (onscreen_native->crtc)); + kms_device = meta_kms_crtc_get_device (kms_crtc); + + switch (renderer_gpu_data->mode) + { + case META_RENDERER_NATIVE_MODE_GBM: + if (meta_renderer_native_has_pending_mode_sets (renderer_native)) + { + meta_topic (META_DEBUG_KMS, + "Postponing primary plane composite update for CRTC %u (%s)", + meta_kms_crtc_get_id (kms_crtc), + meta_kms_device_get_path (kms_device)); + + clutter_frame_set_result (frame, + CLUTTER_FRAME_RESULT_PENDING_PRESENTED); + return; + } + else if (meta_renderer_native_has_pending_mode_set (renderer_native)) + { + meta_topic (META_DEBUG_KMS, "Posting global mode set updates on %s", + meta_kms_device_get_path (kms_device)); + + meta_renderer_native_notify_mode_sets_reset (renderer_native); + meta_renderer_native_post_mode_set_updates (renderer_native); + clutter_frame_set_result (frame, + CLUTTER_FRAME_RESULT_PENDING_PRESENTED); + return; + } + break; +#ifdef HAVE_EGL_DEVICE + case META_RENDERER_NATIVE_MODE_EGL_DEVICE: + if (meta_renderer_native_has_pending_mode_set (renderer_native)) + { + meta_renderer_native_notify_mode_sets_reset (renderer_native); + meta_renderer_native_post_mode_set_updates (renderer_native); + clutter_frame_set_result (frame, + CLUTTER_FRAME_RESULT_PENDING_PRESENTED); + return; + } + break; +#endif + } + + meta_topic (META_DEBUG_KMS, + "Posting primary plane composite update for CRTC %u (%s)", + meta_kms_crtc_get_id (kms_crtc), + meta_kms_device_get_path (kms_device)); + + flags = META_KMS_UPDATE_FLAG_NONE; + kms_feedback = meta_kms_post_pending_update_sync (kms, kms_device, flags); + + switch (meta_kms_feedback_get_result (kms_feedback)) + { + case META_KMS_FEEDBACK_PASSED: + clutter_frame_set_result (frame, + CLUTTER_FRAME_RESULT_PENDING_PRESENTED); + break; + case META_KMS_FEEDBACK_FAILED: + clutter_frame_set_result (frame, + CLUTTER_FRAME_RESULT_PENDING_PRESENTED); + + feedback_error = meta_kms_feedback_get_error (kms_feedback); + if (!g_error_matches (feedback_error, + G_IO_ERROR, + G_IO_ERROR_PERMISSION_DENIED)) + g_warning ("Failed to post KMS update: %s", feedback_error->message); + break; + } +} + +gboolean +meta_onscreen_native_is_buffer_scanout_compatible (CoglOnscreen *onscreen, + uint32_t drm_format, + uint64_t drm_modifier, + uint32_t stride) +{ + CoglOnscreenEgl *onscreen_egl = cogl_onscreen_get_winsys (onscreen); + MetaOnscreenNative *onscreen_native = + cogl_onscreen_egl_get_platform (onscreen_egl); + const MetaCrtcConfig *crtc_config; + MetaDrmBuffer *fb; + struct gbm_bo *gbm_bo; + + crtc_config = meta_crtc_get_config (onscreen_native->crtc); + if (crtc_config->transform != META_MONITOR_TRANSFORM_NORMAL) + return FALSE; + + if (onscreen_native->secondary_gpu_state) + return FALSE; + + if (!onscreen_native->gbm.surface) + return FALSE; + + fb = onscreen_native->gbm.current_fb ? onscreen_native->gbm.current_fb + : onscreen_native->gbm.next_fb; + if (!fb) + return FALSE; + + if (!META_IS_DRM_BUFFER_GBM (fb)) + return FALSE; + + gbm_bo = meta_drm_buffer_gbm_get_bo (META_DRM_BUFFER_GBM (fb)); + + if (gbm_bo_get_format (gbm_bo) != drm_format) + return FALSE; + + if (gbm_bo_get_modifier (gbm_bo) != drm_modifier) + return FALSE; + + if (gbm_bo_get_stride (gbm_bo) != stride) + return FALSE; + + return TRUE; +} + +gboolean +meta_onscreen_native_direct_scanout (CoglOnscreen *onscreen, + CoglScanout *scanout, + CoglFrameInfo *frame_info, + gpointer user_data, + GError **error) +{ + CoglOnscreenEgl *onscreen_egl = cogl_onscreen_get_winsys (onscreen); + MetaOnscreenNative *onscreen_native = + cogl_onscreen_egl_get_platform (onscreen_egl); + MetaGpuKms *render_gpu = onscreen_native->render_gpu; + CoglFramebuffer *framebuffer = COGL_FRAMEBUFFER (onscreen); + CoglContext *cogl_context = cogl_framebuffer_get_context (framebuffer); + CoglRenderer *cogl_renderer = cogl_context->display->renderer; + CoglRendererEGL *cogl_renderer_egl = cogl_renderer->winsys; + MetaRendererNativeGpuData *renderer_gpu_data = cogl_renderer_egl->platform; + MetaRendererNative *renderer_native = renderer_gpu_data->renderer_native; + MetaRenderer *renderer = META_RENDERER (renderer_native); + MetaBackend *backend = meta_renderer_get_backend (renderer); + MetaBackendNative *backend_native = META_BACKEND_NATIVE (backend); + MetaKms *kms = meta_backend_native_get_kms (backend_native); + MetaMonitorManager *monitor_manager = + meta_backend_get_monitor_manager (backend); + MetaPowerSave power_save_mode; + ClutterFrame *frame = user_data; + MetaKmsCrtc *kms_crtc; + MetaKmsDevice *kms_device; + MetaKmsUpdateFlag flags; + g_autoptr (MetaKmsFeedback) kms_feedback = NULL; + const GError *feedback_error; + + power_save_mode = meta_monitor_manager_get_power_save_mode (monitor_manager); + if (power_save_mode != META_POWER_SAVE_ON) + { + g_set_error_literal (error, + COGL_SCANOUT_ERROR, + COGL_SCANOUT_ERROR_INHIBITED, + NULL); + return FALSE; + } + + if (meta_renderer_native_has_pending_mode_set (renderer_native)) + { + g_set_error_literal (error, + COGL_SCANOUT_ERROR, + COGL_SCANOUT_ERROR_INHIBITED, + NULL); + return FALSE; + } + + renderer_gpu_data = meta_renderer_native_get_gpu_data (renderer_native, + render_gpu); + + g_warn_if_fail (renderer_gpu_data->mode == META_RENDERER_NATIVE_MODE_GBM); + g_warn_if_fail (!onscreen_native->gbm.next_fb); + + g_set_object (&onscreen_native->gbm.next_fb, META_DRM_BUFFER (scanout)); + + ensure_crtc_modes (onscreen); + meta_onscreen_native_flip_crtc (onscreen, + onscreen_native->view, + onscreen_native->crtc, + META_KMS_PAGE_FLIP_LISTENER_FLAG_NO_DISCARD); + + kms_crtc = meta_crtc_kms_get_kms_crtc (META_CRTC_KMS (onscreen_native->crtc)); + kms_device = meta_kms_crtc_get_device (kms_crtc); + + meta_topic (META_DEBUG_KMS, + "Posting direct scanout update for CRTC %u (%s)", + meta_kms_crtc_get_id (kms_crtc), + meta_kms_device_get_path (kms_device)); + + flags = META_KMS_UPDATE_FLAG_PRESERVE_ON_ERROR; + kms_feedback = meta_kms_post_pending_update_sync (kms, kms_device, flags); + switch (meta_kms_feedback_get_result (kms_feedback)) + { + case META_KMS_FEEDBACK_PASSED: + clutter_frame_set_result (frame, + CLUTTER_FRAME_RESULT_PENDING_PRESENTED); + break; + case META_KMS_FEEDBACK_FAILED: + feedback_error = meta_kms_feedback_get_error (kms_feedback); + + if (g_error_matches (feedback_error, + G_IO_ERROR, G_IO_ERROR_PERMISSION_DENIED)) + break; + + g_clear_object (&onscreen_native->gbm.next_fb); + g_propagate_error (error, g_error_copy (feedback_error)); + return FALSE; + } + + return TRUE; +} + +static void +add_onscreen_frame_info (MetaCrtc *crtc) +{ + MetaGpu *gpu = meta_crtc_get_gpu (crtc); + MetaBackend *backend = meta_gpu_get_backend (gpu); + ClutterBackend *clutter_backend = meta_backend_get_clutter_backend (backend); + MetaStageNative *stage_native = + meta_clutter_backend_native_get_stage_native (clutter_backend); + MetaRenderer *renderer = meta_backend_get_renderer (backend); + MetaRendererView *view = meta_renderer_get_view_for_crtc (renderer, crtc); + + clutter_stage_cogl_add_onscreen_frame_info (CLUTTER_STAGE_COGL (stage_native), + CLUTTER_STAGE_VIEW (view)); +} + +void +meta_onscreen_native_finish_frame (CoglOnscreen *onscreen, + ClutterFrame *frame) +{ + CoglOnscreenEgl *onscreen_egl = cogl_onscreen_get_winsys (onscreen); + MetaOnscreenNative *onscreen_native = + cogl_onscreen_egl_get_platform (onscreen_egl); + MetaCrtc *crtc = onscreen_native->crtc; + MetaKmsCrtc *kms_crtc = meta_crtc_kms_get_kms_crtc (META_CRTC_KMS (crtc)); + MetaKmsDevice *kms_device = meta_kms_crtc_get_device (kms_crtc);; + MetaKms *kms = meta_kms_device_get_kms (kms_device); + MetaKmsUpdateFlag flags; + MetaKmsUpdate *kms_update; + g_autoptr (MetaKmsFeedback) kms_feedback = NULL; + const GError *error; + + kms_update = meta_kms_get_pending_update (kms, kms_device); + if (!kms_update) + { + clutter_frame_set_result (frame, CLUTTER_FRAME_RESULT_IDLE); + return; + } + + meta_kms_update_add_page_flip_listener (kms_update, + kms_crtc, + &page_flip_listener_vtable, + META_KMS_PAGE_FLIP_LISTENER_FLAG_NONE, + g_object_ref (onscreen_native->view), + g_object_unref); + + flags = META_KMS_UPDATE_FLAG_NONE; + kms_feedback = meta_kms_post_pending_update_sync (kms, + kms_device, + flags); + switch (meta_kms_feedback_get_result (kms_feedback)) + { + case META_KMS_FEEDBACK_PASSED: + add_onscreen_frame_info (crtc); + clutter_frame_set_result (frame, + CLUTTER_FRAME_RESULT_PENDING_PRESENTED); + break; + case META_KMS_FEEDBACK_FAILED: + add_onscreen_frame_info (crtc); + clutter_frame_set_result (frame, + CLUTTER_FRAME_RESULT_PENDING_PRESENTED); + + error = meta_kms_feedback_get_error (kms_feedback); + if (!g_error_matches (error, + G_IO_ERROR, + G_IO_ERROR_PERMISSION_DENIED)) + g_warning ("Failed to post KMS update: %s", error->message); + break; + } +} + +static gboolean +should_surface_be_sharable (CoglOnscreen *onscreen) +{ + CoglOnscreenEgl *onscreen_egl = cogl_onscreen_get_winsys (onscreen); + MetaOnscreenNative *onscreen_native = + cogl_onscreen_egl_get_platform (onscreen_egl); + + if (META_GPU_KMS (meta_crtc_get_gpu (onscreen_native->crtc)) == + onscreen_native->render_gpu) + return FALSE; + else + return TRUE; +} + +static uint32_t +get_gbm_format_from_egl (MetaEgl *egl, + EGLDisplay egl_display, + EGLConfig egl_config) +{ + uint32_t gbm_format; + EGLint native_visual_id; + + if (meta_egl_get_config_attrib (egl, + egl_display, + egl_config, + EGL_NATIVE_VISUAL_ID, + &native_visual_id, + NULL)) + gbm_format = (uint32_t) native_visual_id; + else + g_assert_not_reached (); + + return gbm_format; +} + +static GArray * +get_supported_kms_modifiers (MetaCrtcKms *crtc_kms, + uint32_t format) +{ + GArray *modifiers; + GArray *crtc_mods; + unsigned int i; + + crtc_mods = meta_crtc_kms_get_modifiers (crtc_kms, format); + if (!crtc_mods) + return NULL; + + modifiers = g_array_new (FALSE, FALSE, sizeof (uint64_t)); + + /* + * For each modifier from base_crtc, check if it's available on all other + * CRTCs. + */ + for (i = 0; i < crtc_mods->len; i++) + { + uint64_t modifier = g_array_index (crtc_mods, uint64_t, i); + + g_array_append_val (modifiers, modifier); + } + + if (modifiers->len == 0) + { + g_array_free (modifiers, TRUE); + return NULL; + } + + return modifiers; +} + +static GArray * +get_supported_egl_modifiers (CoglOnscreen *onscreen, + MetaCrtcKms *crtc_kms, + uint32_t format) +{ + CoglOnscreenEgl *onscreen_egl = cogl_onscreen_get_winsys (onscreen); + MetaOnscreenNative *onscreen_native = + cogl_onscreen_egl_get_platform (onscreen_egl); + MetaRendererNative *renderer_native = onscreen_native->renderer_native; + MetaEgl *egl = meta_onscreen_native_get_egl (onscreen_native); + MetaGpu *gpu; + MetaRendererNativeGpuData *renderer_gpu_data; + EGLint num_modifiers; + GArray *modifiers; + GError *error = NULL; + gboolean ret; + + gpu = meta_crtc_get_gpu (META_CRTC (crtc_kms)); + renderer_gpu_data = meta_renderer_native_get_gpu_data (renderer_native, + META_GPU_KMS (gpu)); + + if (!meta_egl_has_extensions (egl, renderer_gpu_data->egl_display, NULL, + "EGL_EXT_image_dma_buf_import_modifiers", + NULL)) + return NULL; + + ret = meta_egl_query_dma_buf_modifiers (egl, renderer_gpu_data->egl_display, + format, 0, NULL, NULL, + &num_modifiers, NULL); + if (!ret || num_modifiers == 0) + return NULL; + + modifiers = g_array_sized_new (FALSE, FALSE, sizeof (uint64_t), + num_modifiers); + ret = meta_egl_query_dma_buf_modifiers (egl, renderer_gpu_data->egl_display, + format, num_modifiers, + (EGLuint64KHR *) modifiers->data, NULL, + &num_modifiers, &error); + + if (!ret) + { + g_warning ("Failed to query DMABUF modifiers: %s", error->message); + g_error_free (error); + g_array_free (modifiers, TRUE); + return NULL; + } + + return modifiers; +} + +static GArray * +get_supported_modifiers (CoglOnscreen *onscreen, + uint32_t format) +{ + CoglOnscreenEgl *onscreen_egl = cogl_onscreen_get_winsys (onscreen); + MetaOnscreenNative *onscreen_native = + cogl_onscreen_egl_get_platform (onscreen_egl); + MetaCrtcKms *crtc_kms = META_CRTC_KMS (onscreen_native->crtc); + MetaGpu *gpu; + g_autoptr (GArray) modifiers = NULL; + + gpu = meta_crtc_get_gpu (META_CRTC (crtc_kms)); + if (gpu == META_GPU (onscreen_native->render_gpu)) + modifiers = get_supported_kms_modifiers (crtc_kms, format); + else + modifiers = get_supported_egl_modifiers (onscreen, crtc_kms, format); + + return g_steal_pointer (&modifiers); +} + +static GArray * +get_supported_kms_formats (CoglOnscreen *onscreen) +{ + CoglOnscreenEgl *onscreen_egl = cogl_onscreen_get_winsys (onscreen); + MetaOnscreenNative *onscreen_native = + cogl_onscreen_egl_get_platform (onscreen_egl); + MetaCrtcKms *crtc_kms = META_CRTC_KMS (onscreen_native->crtc); + + return meta_crtc_kms_copy_drm_format_list (crtc_kms); +} + +static gboolean +create_surfaces_gbm (CoglOnscreen *onscreen, + int width, + int height, + struct gbm_surface **gbm_surface, + EGLSurface *egl_surface, + GError **error) +{ + CoglOnscreenEgl *onscreen_egl = cogl_onscreen_get_winsys (onscreen); + MetaOnscreenNative *onscreen_native = + cogl_onscreen_egl_get_platform (onscreen_egl); + MetaRendererNative *renderer_native = onscreen_native->renderer_native; + MetaEgl *egl = meta_onscreen_native_get_egl (onscreen_native); + CoglFramebuffer *framebuffer = COGL_FRAMEBUFFER (onscreen); + CoglContext *cogl_context = cogl_framebuffer_get_context (framebuffer); + CoglDisplay *cogl_display = cogl_context->display; + CoglDisplayEGL *cogl_display_egl = cogl_display->winsys; + CoglRenderer *cogl_renderer = cogl_display->renderer; + CoglRendererEGL *cogl_renderer_egl = cogl_renderer->winsys; + MetaRendererNativeGpuData *renderer_gpu_data = cogl_renderer_egl->platform; + struct gbm_surface *new_gbm_surface = NULL; + EGLNativeWindowType egl_native_window; + EGLSurface new_egl_surface; + uint32_t format; + GArray *modifiers; + + renderer_gpu_data = + meta_renderer_native_get_gpu_data (renderer_native, + onscreen_native->render_gpu); + + format = get_gbm_format_from_egl (egl, + cogl_renderer_egl->edpy, + cogl_display_egl->egl_config); + + if (meta_renderer_native_use_modifiers (renderer_native)) + modifiers = get_supported_modifiers (onscreen, format); + else + modifiers = NULL; + + if (modifiers) + { + new_gbm_surface = + gbm_surface_create_with_modifiers (renderer_gpu_data->gbm.device, + width, height, format, + (uint64_t *) modifiers->data, + modifiers->len); + g_array_free (modifiers, TRUE); + } + + if (!new_gbm_surface) + { + uint32_t flags = GBM_BO_USE_SCANOUT | GBM_BO_USE_RENDERING; + + if (should_surface_be_sharable (onscreen)) + flags |= GBM_BO_USE_LINEAR; + + new_gbm_surface = gbm_surface_create (renderer_gpu_data->gbm.device, + width, height, + format, + flags); + } + + if (!new_gbm_surface) + { + g_set_error (error, COGL_WINSYS_ERROR, + COGL_WINSYS_ERROR_CREATE_ONSCREEN, + "Failed to allocate surface"); + return FALSE; + } + + egl_native_window = (EGLNativeWindowType) new_gbm_surface; + new_egl_surface = + meta_egl_create_window_surface (egl, + cogl_renderer_egl->edpy, + cogl_display_egl->egl_config, + egl_native_window, + NULL, + error); + if (new_egl_surface == EGL_NO_SURFACE) + { + gbm_surface_destroy (new_gbm_surface); + return FALSE; + } + + *gbm_surface = new_gbm_surface; + *egl_surface = new_egl_surface; + + return TRUE; +} + +#ifdef HAVE_EGL_DEVICE +static gboolean +create_surfaces_egl_device (CoglOnscreen *onscreen, + int width, + int height, + EGLStreamKHR *out_egl_stream, + EGLSurface *out_egl_surface, + GError **error) +{ + CoglFramebuffer *framebuffer = COGL_FRAMEBUFFER (onscreen); + CoglOnscreenEgl *onscreen_egl = cogl_onscreen_get_winsys (onscreen); + MetaOnscreenNative *onscreen_native = + cogl_onscreen_egl_get_platform (onscreen_egl); + CoglContext *cogl_context = cogl_framebuffer_get_context (framebuffer); + CoglDisplay *cogl_display = cogl_context->display; + CoglDisplayEGL *cogl_display_egl = cogl_display->winsys; + CoglRenderer *cogl_renderer = cogl_display->renderer; + CoglRendererEGL *cogl_renderer_egl = cogl_renderer->winsys; + MetaRendererNativeGpuData *renderer_gpu_data = cogl_renderer_egl->platform; + MetaEgl *egl = + meta_renderer_native_get_egl (renderer_gpu_data->renderer_native); + EGLDisplay egl_display = renderer_gpu_data->egl_display; + EGLConfig egl_config; + EGLStreamKHR egl_stream; + EGLSurface egl_surface; + EGLint num_layers; + EGLOutputLayerEXT output_layer; + EGLAttrib output_attribs[3]; + EGLint stream_attribs[] = { + EGL_STREAM_FIFO_LENGTH_KHR, 0, + EGL_CONSUMER_AUTO_ACQUIRE_EXT, EGL_FALSE, + EGL_NONE + }; + EGLint stream_producer_attribs[] = { + EGL_WIDTH, width, + EGL_HEIGHT, height, + EGL_NONE + }; + + egl_stream = meta_egl_create_stream (egl, egl_display, stream_attribs, error); + if (egl_stream == EGL_NO_STREAM_KHR) + return FALSE; + + output_attribs[0] = EGL_DRM_CRTC_EXT; + output_attribs[1] = meta_crtc_get_id (onscreen_native->crtc); + output_attribs[2] = EGL_NONE; + + if (!meta_egl_get_output_layers (egl, egl_display, + output_attribs, + &output_layer, 1, &num_layers, + error)) + { + meta_egl_destroy_stream (egl, egl_display, egl_stream, NULL); + return FALSE; + } + + if (num_layers < 1) + { + meta_egl_destroy_stream (egl, egl_display, egl_stream, NULL); + g_set_error (error, G_IO_ERROR, + G_IO_ERROR_FAILED, + "Unable to find output layers."); + return FALSE; + } + + if (!meta_egl_stream_consumer_output (egl, egl_display, + egl_stream, output_layer, + error)) + { + meta_egl_destroy_stream (egl, egl_display, egl_stream, NULL); + return FALSE; + } + + egl_config = cogl_display_egl->egl_config; + egl_surface = meta_egl_create_stream_producer_surface (egl, + egl_display, + egl_config, + egl_stream, + stream_producer_attribs, + error); + if (egl_surface == EGL_NO_SURFACE) + { + meta_egl_destroy_stream (egl, egl_display, egl_stream, NULL); + return FALSE; + } + + *out_egl_stream = egl_stream; + *out_egl_surface = egl_surface; + + return TRUE; +} +#endif /* HAVE_EGL_DEVICE */ + +void +meta_onscreen_native_set_view (CoglOnscreen *onscreen, + MetaRendererView *view) +{ + CoglOnscreenEgl *onscreen_egl; + MetaOnscreenNative *onscreen_native; + + onscreen_egl = cogl_onscreen_get_winsys (onscreen); + onscreen_native = + cogl_onscreen_egl_get_platform (onscreen_egl); + onscreen_native->view = view; +} + +gboolean +meta_renderer_native_init_onscreen (CoglOnscreen *onscreen, + GError **error) +{ + CoglFramebuffer *framebuffer = COGL_FRAMEBUFFER (onscreen); + CoglContext *cogl_context = cogl_framebuffer_get_context (framebuffer); + CoglDisplay *cogl_display = cogl_context->display; + CoglDisplayEGL *cogl_display_egl = cogl_display->winsys; + CoglOnscreenEgl *onscreen_egl; + MetaOnscreenNative *onscreen_native; + + g_return_val_if_fail (cogl_display_egl->egl_context, FALSE); + + onscreen_egl = cogl_onscreen_egl_new (); + cogl_onscreen_set_winsys (onscreen, onscreen_egl); + + onscreen_native = g_slice_new0 (MetaOnscreenNative); + cogl_onscreen_egl_set_platform (onscreen_egl, onscreen_native); + + /* + * Don't actually initialize anything here, since we may not have the + * information available yet, and there is no way to pass it at this stage. + * To properly allocate a MetaOnscreenNative, the caller must call + * meta_onscreen_native_allocate() after cogl_framebuffer_allocate(). + * + * TODO: Turn CoglFramebuffer/CoglOnscreen into GObjects, so it's possible + * to add backend specific properties. + */ + + return TRUE; +} + +static gboolean +init_secondary_gpu_state_gpu_copy_mode (MetaRendererNative *renderer_native, + CoglOnscreen *onscreen, + MetaRendererNativeGpuData *renderer_gpu_data, + GError **error) +{ + CoglFramebuffer *framebuffer = COGL_FRAMEBUFFER (onscreen); + CoglOnscreenEgl *onscreen_egl = cogl_onscreen_get_winsys (onscreen); + MetaOnscreenNative *onscreen_native = + cogl_onscreen_egl_get_platform (onscreen_egl); + MetaEgl *egl = meta_onscreen_native_get_egl (onscreen_native); + int width, height; + EGLNativeWindowType egl_native_window; + struct gbm_surface *gbm_surface; + EGLSurface egl_surface; + MetaOnscreenNativeSecondaryGpuState *secondary_gpu_state; + MetaGpuKms *gpu_kms; + uint32_t format; + + width = cogl_framebuffer_get_width (framebuffer); + height = cogl_framebuffer_get_height (framebuffer); + format = get_gbm_format_from_egl (egl, + renderer_gpu_data->egl_display, + renderer_gpu_data->secondary.egl_config); + + gbm_surface = gbm_surface_create (renderer_gpu_data->gbm.device, + width, height, + format, + GBM_BO_USE_SCANOUT | GBM_BO_USE_RENDERING); + if (!gbm_surface) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "Failed to create gbm_surface: %s", strerror (errno)); + return FALSE; + } + + egl_native_window = (EGLNativeWindowType) gbm_surface; + egl_surface = + meta_egl_create_window_surface (egl, + renderer_gpu_data->egl_display, + renderer_gpu_data->secondary.egl_config, + egl_native_window, + NULL, + error); + if (egl_surface == EGL_NO_SURFACE) + { + gbm_surface_destroy (gbm_surface); + return FALSE; + } + + secondary_gpu_state = g_new0 (MetaOnscreenNativeSecondaryGpuState, 1); + + gpu_kms = META_GPU_KMS (meta_crtc_get_gpu (onscreen_native->crtc)); + secondary_gpu_state->gpu_kms = gpu_kms; + secondary_gpu_state->renderer_gpu_data = renderer_gpu_data; + secondary_gpu_state->gbm.surface = gbm_surface; + secondary_gpu_state->egl_surface = egl_surface; + + onscreen_native->secondary_gpu_state = secondary_gpu_state; + + return TRUE; +} + +static uint32_t +pick_secondary_gpu_framebuffer_format_for_cpu (CoglOnscreen *onscreen) +{ + /* + * cogl_framebuffer_read_pixels_into_bitmap () supported formats in + * preference order. Ideally these should depend on the render buffer + * format copy_shared_framebuffer_cpu () will be reading from but + * alpha channel ignored. + */ + static const uint32_t preferred_formats[] = + { + /* + * DRM_FORMAT_XBGR8888 a.k.a GL_RGBA, GL_UNSIGNED_BYTE on + * little-endian is possibly the most optimized glReadPixels + * output format. glReadPixels cannot avoid manufacturing an alpha + * channel if the render buffer does not have one and converting + * to ABGR8888 may be more optimized than ARGB8888. + */ + DRM_FORMAT_XBGR8888, + /* The rest are other fairly commonly used formats in OpenGL. */ + DRM_FORMAT_XRGB8888, + }; + g_autoptr (GArray) formats = NULL; + size_t k; + unsigned int i; + uint32_t drm_format; + + formats = get_supported_kms_formats (onscreen); + + /* Check if any of our preferred formats are supported. */ + for (k = 0; k < G_N_ELEMENTS (preferred_formats); k++) + { + g_assert (meta_cogl_pixel_format_from_drm_format (preferred_formats[k], + NULL, + NULL)); + + for (i = 0; i < formats->len; i++) + { + drm_format = g_array_index (formats, uint32_t, i); + + if (drm_format == preferred_formats[k]) + return drm_format; + } + } + + /* + * Otherwise just pick an arbitrary format we recognize. The formats + * list is not in any specific order and we don't know any better + * either. + */ + for (i = 0; i < formats->len; i++) + { + drm_format = g_array_index (formats, uint32_t, i); + + if (meta_cogl_pixel_format_from_drm_format (drm_format, NULL, NULL)) + return drm_format; + } + + return DRM_FORMAT_INVALID; +} + +static gboolean +init_secondary_gpu_state_cpu_copy_mode (MetaRendererNative *renderer_native, + CoglOnscreen *onscreen, + MetaRendererNativeGpuData *renderer_gpu_data, + GError **error) +{ + CoglFramebuffer *framebuffer = COGL_FRAMEBUFFER (onscreen); + CoglOnscreenEgl *onscreen_egl = cogl_onscreen_get_winsys (onscreen); + MetaOnscreenNative *onscreen_native = + cogl_onscreen_egl_get_platform (onscreen_egl); + MetaOnscreenNativeSecondaryGpuState *secondary_gpu_state; + MetaGpuKms *gpu_kms; + MetaKmsDevice *kms_device; + int width, height; + unsigned int i; + uint32_t drm_format; + MetaDrmFormatBuf tmp; + + drm_format = pick_secondary_gpu_framebuffer_format_for_cpu (onscreen); + if (drm_format == DRM_FORMAT_INVALID) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "Could not find a suitable pixel format in CPU copy mode"); + return FALSE; + } + + width = cogl_framebuffer_get_width (framebuffer); + height = cogl_framebuffer_get_height (framebuffer); + + gpu_kms = META_GPU_KMS (meta_crtc_get_gpu (onscreen_native->crtc)); + kms_device = meta_gpu_kms_get_kms_device (gpu_kms); + g_debug ("Secondary GPU %s using DRM format '%s' (0x%x) for a %dx%d output.", + meta_gpu_kms_get_file_path (gpu_kms), + meta_drm_format_to_string (&tmp, drm_format), + drm_format, + width, height); + + secondary_gpu_state = g_new0 (MetaOnscreenNativeSecondaryGpuState, 1); + secondary_gpu_state->renderer_gpu_data = renderer_gpu_data; + secondary_gpu_state->gpu_kms = gpu_kms; + secondary_gpu_state->egl_surface = EGL_NO_SURFACE; + + for (i = 0; i < G_N_ELEMENTS (secondary_gpu_state->cpu.dumb_fbs); i++) + { + secondary_gpu_state->cpu.dumb_fbs[i] = + meta_drm_buffer_dumb_new (kms_device, + width, height, + drm_format, + error); + if (!secondary_gpu_state->cpu.dumb_fbs[i]) + { + secondary_gpu_state_free (secondary_gpu_state); + return FALSE; + } + } + + /* + * 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; + + onscreen_native->secondary_gpu_state = secondary_gpu_state; + + return TRUE; +} + +static gboolean +init_secondary_gpu_state (MetaRendererNative *renderer_native, + CoglOnscreen *onscreen, + GError **error) +{ + CoglOnscreenEgl *onscreen_egl = cogl_onscreen_get_winsys (onscreen); + MetaOnscreenNative *onscreen_native = + cogl_onscreen_egl_get_platform (onscreen_egl); + MetaGpu *gpu = meta_crtc_get_gpu (onscreen_native->crtc); + MetaRendererNativeGpuData *renderer_gpu_data; + + renderer_gpu_data = meta_renderer_native_get_gpu_data (renderer_native, + META_GPU_KMS (gpu)); + + switch (renderer_gpu_data->secondary.copy_mode) + { + case META_SHARED_FRAMEBUFFER_COPY_MODE_SECONDARY_GPU: + if (!init_secondary_gpu_state_gpu_copy_mode (renderer_native, + onscreen, + renderer_gpu_data, + 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, + renderer_gpu_data, + error)) + return FALSE; + break; + } + + return TRUE; +} + +CoglOnscreen * +meta_onscreen_native_new (MetaRendererNative *renderer_native, + MetaGpuKms *render_gpu, + MetaOutput *output, + MetaCrtc *crtc, + CoglContext *context, + int width, + int height, + GError **error) +{ + CoglOnscreen *onscreen; + CoglOnscreenEgl *onscreen_egl; + MetaOnscreenNative *onscreen_native; + + onscreen = cogl_onscreen_new (context, width, height); + + if (!cogl_framebuffer_allocate (COGL_FRAMEBUFFER (onscreen), error)) + { + g_object_unref (onscreen); + return NULL; + } + + onscreen_egl = cogl_onscreen_get_winsys (onscreen); + onscreen_native = + cogl_onscreen_egl_get_platform (onscreen_egl); + onscreen_native->renderer_native = renderer_native; + onscreen_native->render_gpu = render_gpu; + onscreen_native->output = output; + onscreen_native->crtc = crtc; + + if (META_GPU_KMS (meta_crtc_get_gpu (crtc)) != render_gpu) + { + if (!init_secondary_gpu_state (renderer_native, onscreen, error)) + { + g_object_unref (onscreen); + return NULL; + } + } + + return onscreen; +} + +gboolean +meta_onscreen_native_allocate (CoglOnscreen *onscreen, + GError **error) +{ + CoglFramebuffer *framebuffer = COGL_FRAMEBUFFER (onscreen); + CoglOnscreenEgl *onscreen_egl = cogl_onscreen_get_winsys (onscreen); + MetaOnscreenNative *onscreen_native = + cogl_onscreen_egl_get_platform (onscreen_egl); + MetaRendererNativeGpuData *renderer_gpu_data; + struct gbm_surface *gbm_surface; + EGLSurface egl_surface; + int width; + int height; +#ifdef HAVE_EGL_DEVICE + MetaKmsDevice *render_kms_device; + EGLStreamKHR egl_stream; +#endif + + /* If a kms_fd is set then the display width and height + * won't be available until meta_renderer_native_set_layout + * is called. In that case, defer creating the surface + * until then. + */ + width = cogl_framebuffer_get_width (framebuffer); + height = cogl_framebuffer_get_height (framebuffer); + if (width == 0 || height == 0) + return TRUE; + + renderer_gpu_data = + meta_renderer_native_get_gpu_data (onscreen_native->renderer_native, + onscreen_native->render_gpu); + switch (renderer_gpu_data->mode) + { + case META_RENDERER_NATIVE_MODE_GBM: + if (!create_surfaces_gbm (onscreen, + width, height, + &gbm_surface, + &egl_surface, + error)) + return FALSE; + + onscreen_native->gbm.surface = gbm_surface; + cogl_onscreen_egl_set_egl_surface (onscreen_egl, egl_surface); + break; +#ifdef HAVE_EGL_DEVICE + case META_RENDERER_NATIVE_MODE_EGL_DEVICE: + render_kms_device = + meta_gpu_kms_get_kms_device (onscreen_native->render_gpu); + onscreen_native->egl.dumb_fb = + meta_drm_buffer_dumb_new (render_kms_device, + width, height, + DRM_FORMAT_XRGB8888, + error); + if (!onscreen_native->egl.dumb_fb) + return FALSE; + + if (!create_surfaces_egl_device (onscreen, + width, height, + &egl_stream, + &egl_surface, + error)) + return FALSE; + + onscreen_native->egl.stream = egl_stream; + cogl_onscreen_egl_set_egl_surface (onscreen_egl, egl_surface); + break; +#endif /* HAVE_EGL_DEVICE */ + } + + return TRUE; +} + +static void +destroy_egl_surface (CoglOnscreen *onscreen) +{ + CoglOnscreenEgl *onscreen_egl = cogl_onscreen_get_winsys (onscreen); + EGLSurface egl_surface; + + egl_surface = cogl_onscreen_egl_get_egl_surface (onscreen_egl); + if (cogl_onscreen_egl_get_egl_surface (onscreen_egl) != EGL_NO_SURFACE) + { + MetaOnscreenNative *onscreen_native = + cogl_onscreen_egl_get_platform (onscreen_egl); + MetaEgl *egl = meta_onscreen_native_get_egl (onscreen_native); + CoglFramebuffer *framebuffer = COGL_FRAMEBUFFER (onscreen); + CoglContext *cogl_context = cogl_framebuffer_get_context (framebuffer); + CoglRenderer *cogl_renderer = cogl_context->display->renderer; + CoglRendererEGL *cogl_renderer_egl = cogl_renderer->winsys; + + meta_egl_destroy_surface (egl, + cogl_renderer_egl->edpy, + egl_surface, + NULL); + cogl_onscreen_egl_set_egl_surface (onscreen_egl, EGL_NO_SURFACE); + } +} + +void +meta_renderer_native_release_onscreen (CoglOnscreen *onscreen) +{ + CoglFramebuffer *framebuffer = COGL_FRAMEBUFFER (onscreen); + CoglContext *cogl_context = cogl_framebuffer_get_context (framebuffer); + CoglDisplay *cogl_display = cogl_context_get_display (cogl_context); + CoglDisplayEGL *cogl_display_egl = cogl_display->winsys; + CoglOnscreenEgl *onscreen_egl = cogl_onscreen_get_winsys (onscreen); + MetaOnscreenNative *onscreen_native; + MetaRendererNative *renderer_native; + MetaRendererNativeGpuData *renderer_gpu_data; + EGLSurface egl_surface; + + /* If we never successfully allocated then there's nothing to do */ + if (onscreen_egl == NULL) + return; + + onscreen_native = + cogl_onscreen_egl_get_platform (onscreen_egl); + renderer_native = onscreen_native->renderer_native; + + egl_surface = cogl_onscreen_egl_get_egl_surface (onscreen_egl); + if (egl_surface != EGL_NO_SURFACE && + (cogl_display_egl->current_draw_surface == egl_surface || + cogl_display_egl->current_read_surface == egl_surface)) + { + if (!_cogl_winsys_egl_make_current (cogl_display, + cogl_display_egl->dummy_surface, + cogl_display_egl->dummy_surface, + cogl_display_egl->egl_context)) + g_warning ("Failed to clear current context"); + } + + renderer_gpu_data = + meta_renderer_native_get_gpu_data (renderer_native, + onscreen_native->render_gpu); + switch (renderer_gpu_data->mode) + { + case META_RENDERER_NATIVE_MODE_GBM: + /* flip state takes a reference on the onscreen so there should + * never be outstanding flips when we reach here. */ + g_return_if_fail (onscreen_native->gbm.next_fb == NULL); + + free_current_bo (onscreen); + + destroy_egl_surface (onscreen); + + if (onscreen_native->gbm.surface) + { + gbm_surface_destroy (onscreen_native->gbm.surface); + onscreen_native->gbm.surface = NULL; + } + break; +#ifdef HAVE_EGL_DEVICE + case META_RENDERER_NATIVE_MODE_EGL_DEVICE: + g_clear_object (&onscreen_native->egl.dumb_fb); + + destroy_egl_surface (onscreen); + + if (onscreen_native->egl.stream != EGL_NO_STREAM_KHR) + { + MetaEgl *egl = meta_onscreen_native_get_egl (onscreen_native); + CoglRenderer *cogl_renderer = cogl_context->display->renderer; + CoglRendererEGL *cogl_renderer_egl = cogl_renderer->winsys; + + meta_egl_destroy_stream (egl, + cogl_renderer_egl->edpy, + onscreen_native->egl.stream, + NULL); + onscreen_native->egl.stream = EGL_NO_STREAM_KHR; + } + break; +#endif /* HAVE_EGL_DEVICE */ + } + + g_clear_pointer (&onscreen_native->secondary_gpu_state, + secondary_gpu_state_free); + + g_slice_free (MetaOnscreenNative, onscreen_native); + cogl_onscreen_egl_free (cogl_onscreen_get_winsys (onscreen)); + cogl_onscreen_set_winsys (onscreen, NULL); +} |