/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ /* * Copyright (C) 2011 Intel Corporation. * Copyright (C) 2016 Red Hat * * 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. * * Authors: * Rob Bradford (from cogl-winsys-egl-kms.c) * Kristian Høgsberg (from eglkms.c) * Benjamin Franzke (from eglkms.c) * Robert Bragg (from cogl-winsys-egl-kms.c) * Neil Roberts (from cogl-winsys-egl-kms.c) * Jonas Ådahl * */ #include "config.h" #include #include #include #include #include #include #include #include #include #include "backends/meta-backend-private.h" #include "backends/meta-renderer-view.h" #include "backends/native/meta-monitor-manager-kms.h" #include "backends/native/meta-renderer-native.h" #include "cogl/cogl.h" #include "core/boxes-private.h" enum { PROP_0, PROP_KMS_FD, PROP_KMS_FILE_PATH, PROP_LAST }; typedef struct _MetaOnscreenNative { struct { struct gbm_surface *surface; uint32_t current_fb_id; uint32_t next_fb_id; struct gbm_bo *current_bo; struct gbm_bo *next_bo; } gbm; gboolean pending_swap_notify; gboolean pending_set_crtc; MetaRendererView *view; int pending_flips; } MetaOnscreenNative; struct _MetaRendererNative { MetaRenderer parent; int kms_fd; char *kms_file_path; EGLDisplay egl_display; struct { struct gbm_device *device; } gbm; CoglClosure *swap_notify_idle; int64_t frame_counter; }; static void initable_iface_init (GInitableIface *initable_iface); G_DEFINE_TYPE_WITH_CODE (MetaRendererNative, meta_renderer_native, META_TYPE_RENDERER, G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE, initable_iface_init)) static const CoglWinsysEGLVtable _cogl_winsys_egl_vtable; static const CoglWinsysVtable *parent_vtable; static void meta_renderer_native_disconnect (CoglRenderer *cogl_renderer) { CoglRendererEGL *egl_renderer = cogl_renderer->winsys; if (egl_renderer->edpy != EGL_NO_DISPLAY) eglTerminate (egl_renderer->edpy); g_slice_free (CoglRendererEGL, egl_renderer); } static void flush_pending_swap_notify (CoglFramebuffer *framebuffer) { if (framebuffer->type == COGL_FRAMEBUFFER_TYPE_ONSCREEN) { CoglOnscreen *onscreen = COGL_ONSCREEN (framebuffer); CoglOnscreenEGL *egl_onscreen = onscreen->winsys; MetaOnscreenNative *onscreen_native = egl_onscreen->platform; if (onscreen_native->pending_swap_notify) { CoglFrameInfo *info = g_queue_pop_head (&onscreen->pending_frame_infos); _cogl_onscreen_notify_frame_sync (onscreen, info); _cogl_onscreen_notify_complete (onscreen, info); onscreen_native->pending_swap_notify = FALSE; cogl_object_unref (onscreen); cogl_object_unref (info); } } } static void flush_pending_swap_notify_idle (void *user_data) { CoglContext *cogl_context = user_data; CoglRendererEGL *egl_renderer = cogl_context->display->renderer->winsys; MetaRendererNative *renderer_native = egl_renderer->platform; GList *l; /* This needs to be disconnected before invoking the callbacks in * case the callbacks cause it to be queued again */ _cogl_closure_disconnect (renderer_native->swap_notify_idle); renderer_native->swap_notify_idle = NULL; l = cogl_context->framebuffers; while (l) { GList *next = l->next; CoglFramebuffer *framebuffer = l->data; flush_pending_swap_notify (framebuffer); l = next; } } static void free_current_bo (CoglOnscreen *onscreen) { CoglOnscreenEGL *egl_onscreen = onscreen->winsys; MetaOnscreenNative *onscreen_native = egl_onscreen->platform; CoglContext *cogl_context = COGL_FRAMEBUFFER (onscreen)->context; CoglRenderer *cogl_renderer = cogl_context->display->renderer; CoglRendererEGL *egl_renderer = cogl_renderer->winsys; MetaRendererNative *renderer_native = egl_renderer->platform; if (onscreen_native->gbm.current_fb_id) { drmModeRmFB (renderer_native->kms_fd, onscreen_native->gbm.current_fb_id); onscreen_native->gbm.current_fb_id = 0; } if (onscreen_native->gbm.current_bo) { gbm_surface_release_buffer (onscreen_native->gbm.surface, onscreen_native->gbm.current_bo); onscreen_native->gbm.current_bo = NULL; } } static void meta_onscreen_native_queue_swap_notify (CoglOnscreen *onscreen) { CoglOnscreenEGL *egl_onscreen = onscreen->winsys; MetaOnscreenNative *onscreen_native = egl_onscreen->platform; MetaBackend *backend = meta_get_backend (); ClutterBackend *clutter_backend = meta_backend_get_clutter_backend (backend); CoglContext *cogl_context = clutter_backend_get_cogl_context (clutter_backend); CoglRenderer *cogl_renderer = cogl_context->display->renderer; CoglRendererEGL *egl_renderer = cogl_renderer->winsys; MetaRendererNative *renderer_native = egl_renderer->platform; /* We only want to notify that the swap is complete when the * application calls cogl_context_dispatch so instead of * immediately notifying we queue an idle callback */ if (!renderer_native->swap_notify_idle) { renderer_native->swap_notify_idle = _cogl_poll_renderer_add_idle (cogl_renderer, flush_pending_swap_notify_idle, cogl_context, NULL); } /* * The framebuffer will have its own referenc while the swap notify is * pending. Otherwise when destroying the view would drop the pending * notification with if the destruction happens before the idle callback * is invoked. */ cogl_object_ref (onscreen); onscreen_native->pending_swap_notify = TRUE; } static gboolean meta_renderer_native_connect (CoglRenderer *cogl_renderer, GError **error) { MetaBackend *backend = meta_get_backend (); MetaRenderer *renderer = meta_backend_get_renderer (backend); MetaRendererNative *renderer_native = META_RENDERER_NATIVE (renderer); CoglRendererEGL *egl_renderer; cogl_renderer->winsys = g_slice_new0 (CoglRendererEGL); egl_renderer = cogl_renderer->winsys; egl_renderer->platform_vtable = &_cogl_winsys_egl_vtable; egl_renderer->platform = renderer_native; egl_renderer->edpy = renderer_native->egl_display; if (!_cogl_winsys_egl_renderer_connect_common (cogl_renderer, error)) goto fail; return TRUE; fail: meta_renderer_native_disconnect (cogl_renderer); return FALSE; } static int meta_renderer_native_add_egl_config_attributes (CoglDisplay *cogl_display, CoglFramebufferConfig *config, EGLint *attributes) { int i = 0; attributes[i++] = EGL_SURFACE_TYPE; attributes[i++] = EGL_WINDOW_BIT; return i; } static gboolean meta_renderer_native_setup_egl_display (CoglDisplay *cogl_display, GError **error) { CoglDisplayEGL *egl_display = cogl_display->winsys; CoglRendererEGL *egl_renderer = cogl_display->renderer->winsys; MetaRendererNative *renderer_native = egl_renderer->platform; egl_display->platform = renderer_native; /* Force a full modeset / drmModeSetCrtc on * the first swap buffers call. */ meta_renderer_native_queue_modes_reset (renderer_native); return TRUE; } static void meta_renderer_native_destroy_egl_display (CoglDisplay *cogl_display) { } static EGLSurface create_dummy_pbuffer_surface (EGLDisplay egl_display, GError **error) { MetaBackend *backend = meta_get_backend (); MetaEgl *egl = meta_backend_get_egl (backend); EGLConfig pbuffer_config; static const EGLint pbuffer_config_attribs[] = { EGL_SURFACE_TYPE, EGL_PBUFFER_BIT, EGL_RED_SIZE, 1, EGL_GREEN_SIZE, 1, EGL_BLUE_SIZE, 1, EGL_ALPHA_SIZE, 0, EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, EGL_NONE }; static const EGLint pbuffer_attribs[] = { EGL_WIDTH, 16, EGL_HEIGHT, 16, EGL_NONE }; if (!meta_egl_choose_config (egl, egl_display, pbuffer_config_attribs, &pbuffer_config, error)) return EGL_NO_SURFACE; return meta_egl_create_pbuffer_surface (egl, egl_display, pbuffer_config, pbuffer_attribs, error); } static gboolean meta_renderer_native_egl_context_created (CoglDisplay *cogl_display, GError **error) { CoglDisplayEGL *egl_display = cogl_display->winsys; CoglRenderer *cogl_renderer = cogl_display->renderer; CoglRendererEGL *egl_renderer = cogl_renderer->winsys; if ((egl_renderer->private_features & COGL_EGL_WINSYS_FEATURE_SURFACELESS_CONTEXT) == 0) { egl_display->dummy_surface = create_dummy_pbuffer_surface (egl_renderer->edpy, error); if (egl_display->dummy_surface == EGL_NO_SURFACE) return FALSE; } if (!_cogl_winsys_egl_make_current (cogl_display, egl_display->dummy_surface, egl_display->dummy_surface, egl_display->egl_context)) { _cogl_set_error (error, COGL_WINSYS_ERROR, COGL_WINSYS_ERROR_CREATE_CONTEXT, "Failed to make context current"); return FALSE; } return TRUE; } static void meta_renderer_native_egl_cleanup_context (CoglDisplay *cogl_display) { CoglDisplayEGL *egl_display = cogl_display->winsys; CoglRenderer *cogl_renderer = cogl_display->renderer; CoglRendererEGL *egl_renderer = cogl_renderer->winsys; if (egl_display->dummy_surface != EGL_NO_SURFACE) { eglDestroySurface (egl_renderer->edpy, egl_display->dummy_surface); egl_display->dummy_surface = EGL_NO_SURFACE; } } static void meta_onscreen_native_swap_drm_fb (CoglOnscreen *onscreen) { CoglOnscreenEGL *egl_onscreen = onscreen->winsys; MetaOnscreenNative *onscreen_native = egl_onscreen->platform; free_current_bo (onscreen); onscreen_native->gbm.current_fb_id = onscreen_native->gbm.next_fb_id; onscreen_native->gbm.next_fb_id = 0; onscreen_native->gbm.current_bo = onscreen_native->gbm.next_bo; onscreen_native->gbm.next_bo = NULL; } static void on_crtc_flipped (GClosure *closure, MetaRendererView *view) { ClutterStageView *stage_view = CLUTTER_STAGE_VIEW (view); CoglFramebuffer *framebuffer = clutter_stage_view_get_onscreen (stage_view); CoglOnscreen *onscreen = COGL_ONSCREEN (framebuffer); CoglOnscreenEGL *egl_onscreen = onscreen->winsys; MetaOnscreenNative *onscreen_native = egl_onscreen->platform; onscreen_native->pending_flips--; if (onscreen_native->pending_flips == 0) { meta_onscreen_native_queue_swap_notify (onscreen); meta_onscreen_native_swap_drm_fb (onscreen); } } static void flip_closure_destroyed (MetaRendererView *view) { ClutterStageView *stage_view = CLUTTER_STAGE_VIEW (view); CoglFramebuffer *framebuffer = clutter_stage_view_get_onscreen (stage_view); CoglOnscreen *onscreen = COGL_ONSCREEN (framebuffer); CoglOnscreenEGL *egl_onscreen = onscreen->winsys; MetaOnscreenNative *onscreen_native = egl_onscreen->platform; if (onscreen_native->gbm.next_fb_id) { MetaBackend *backend = meta_get_backend (); MetaRenderer *renderer = meta_backend_get_renderer (backend); MetaRendererNative *renderer_native = META_RENDERER_NATIVE (renderer); drmModeRmFB (renderer_native->kms_fd, onscreen_native->gbm.next_fb_id); gbm_surface_release_buffer (onscreen_native->gbm.surface, onscreen_native->gbm.next_bo); onscreen_native->gbm.next_bo = NULL; onscreen_native->gbm.next_fb_id = 0; meta_onscreen_native_queue_swap_notify (onscreen); } g_object_unref (view); } static void meta_onscreen_native_flip_crtc (MetaOnscreenNative *onscreen_native, GClosure *flip_closure, MetaCRTC *crtc, int x, int y, gboolean *fb_in_use) { MetaBackend *backend = meta_get_backend (); MetaMonitorManager *monitor_manager = meta_backend_get_monitor_manager (backend); MetaMonitorManagerKms *monitor_manager_kms = META_MONITOR_MANAGER_KMS (monitor_manager); if (!meta_monitor_manager_kms_is_crtc_active (monitor_manager_kms, crtc)) { *fb_in_use = FALSE; return; } if (meta_monitor_manager_kms_flip_crtc (monitor_manager_kms, crtc, x, y, onscreen_native->gbm.next_fb_id, flip_closure)) onscreen_native->pending_flips++; *fb_in_use = TRUE; } static void meta_onscreen_native_set_crtc_modes (MetaOnscreenNative *onscreen_native) { MetaBackend *backend = meta_get_backend (); MetaMonitorManager *monitor_manager = meta_backend_get_monitor_manager (backend); MetaMonitorManagerKms *monitor_manager_kms = META_MONITOR_MANAGER_KMS (monitor_manager); MetaRendererView *view = onscreen_native->view; uint32_t next_fb_id = onscreen_native->gbm.next_fb_id; MetaMonitorInfo *monitor_info; monitor_info = meta_renderer_view_get_monitor_info (view); if (monitor_info) { unsigned int i; for (i = 0; i < monitor_manager->n_crtcs; i++) { MetaCRTC *crtc = &monitor_manager->crtcs[i]; int x = crtc->rect.x - monitor_info->rect.x; int y = crtc->rect.y - monitor_info->rect.y; if (crtc->logical_monitor != monitor_info) continue; meta_monitor_manager_kms_apply_crtc_mode (monitor_manager_kms, crtc, x, y, next_fb_id); } } else { unsigned int i; for (i = 0; i < monitor_manager->n_crtcs; i++) { MetaCRTC *crtc = &monitor_manager->crtcs[i]; meta_monitor_manager_kms_apply_crtc_mode (monitor_manager_kms, crtc, crtc->rect.x, crtc->rect.y, next_fb_id); } } } static void meta_onscreen_native_flip_crtcs (CoglOnscreen *onscreen) { CoglOnscreenEGL *egl_onscreen = onscreen->winsys; MetaOnscreenNative *onscreen_native = egl_onscreen->platform; MetaBackend *backend = meta_get_backend (); MetaMonitorManager *monitor_manager = meta_backend_get_monitor_manager (backend); MetaRendererView *view = onscreen_native->view; GClosure *flip_closure; MetaMonitorInfo *monitor_info; gboolean fb_in_use = FALSE; /* * Create a closure that either will be invoked or destructed. * Invoking the closure represents a completed flip. If the closure * is destructed before being invoked, the framebuffer references will be * cleaned up accordingly. * * Each successful flip will each own one reference to the closure, thus keep * it alive until either invoked or destructed. If flipping failed, the * closure will be destructed before this function goes out of scope. */ flip_closure = g_cclosure_new (G_CALLBACK (on_crtc_flipped), g_object_ref (view), (GClosureNotify) flip_closure_destroyed); g_closure_set_marshal (flip_closure, g_cclosure_marshal_generic); /* Either flip the CRTC's of the monitor info, if we are drawing just part * of the stage, or all of the CRTC's if we are drawing the whole stage. */ monitor_info = meta_renderer_view_get_monitor_info (view); if (monitor_info) { unsigned int i; for (i = 0; i < monitor_manager->n_crtcs; i++) { MetaCRTC *crtc = &monitor_manager->crtcs[i]; int x = crtc->rect.x - monitor_info->rect.x; int y = crtc->rect.y - monitor_info->rect.y; if (crtc->logical_monitor != monitor_info) continue; meta_onscreen_native_flip_crtc (onscreen_native, flip_closure, crtc, x, y, &fb_in_use); } } else { unsigned int i; for (i = 0; i < monitor_manager->n_crtcs; i++) { MetaCRTC *crtc = &monitor_manager->crtcs[i]; meta_onscreen_native_flip_crtc (onscreen_native, flip_closure, crtc, crtc->rect.x, crtc->rect.y, &fb_in_use); } } /* * If the framebuffer is in use, but we don't have any pending flips it means * that flipping is not supported and we set the next framebuffer directly. * Since we won't receive a flip callback, lets just notify listeners * directly. */ if (fb_in_use && onscreen_native->pending_flips == 0) { meta_onscreen_native_queue_swap_notify (onscreen); meta_onscreen_native_swap_drm_fb (onscreen); } g_closure_unref (flip_closure); } static void meta_onscreen_native_swap_buffers_with_damage (CoglOnscreen *onscreen, const int *rectangles, int n_rectangles) { MetaBackend *backend = meta_get_backend (); MetaMonitorManager *monitor_manager = meta_backend_get_monitor_manager (backend); MetaMonitorManagerKms *monitor_manager_kms = META_MONITOR_MANAGER_KMS (monitor_manager); CoglContext *cogl_context = COGL_FRAMEBUFFER (onscreen)->context; CoglRenderer *cogl_renderer = cogl_context->display->renderer; CoglRendererEGL *egl_renderer = cogl_renderer->winsys; MetaRendererNative *renderer_native = egl_renderer->platform; CoglOnscreenEGL *egl_onscreen = onscreen->winsys; MetaOnscreenNative *onscreen_native = egl_onscreen->platform; CoglFrameInfo *frame_info; uint32_t handle, stride; frame_info = g_queue_peek_tail (&onscreen->pending_frame_infos); frame_info->global_frame_counter = renderer_native->frame_counter; /* If we already have a pending swap then block until it completes */ while (onscreen_native->gbm.next_fb_id != 0) meta_monitor_manager_kms_wait_for_flip (monitor_manager_kms); parent_vtable->onscreen_swap_buffers_with_damage (onscreen, rectangles, n_rectangles); /* Now we need to set the CRTC to whatever is the front buffer */ onscreen_native->gbm.next_bo = gbm_surface_lock_front_buffer (onscreen_native->gbm.surface); stride = gbm_bo_get_stride (onscreen_native->gbm.next_bo); handle = gbm_bo_get_handle (onscreen_native->gbm.next_bo).u32; if (drmModeAddFB (renderer_native->kms_fd, cogl_framebuffer_get_width (COGL_FRAMEBUFFER (onscreen)), cogl_framebuffer_get_height (COGL_FRAMEBUFFER (onscreen)), 24, /* depth */ 32, /* bpp */ stride, handle, &onscreen_native->gbm.next_fb_id)) { g_warning ("Failed to create new back buffer handle: %m"); gbm_surface_release_buffer (onscreen_native->gbm.surface, onscreen_native->gbm.next_bo); onscreen_native->gbm.next_bo = NULL; onscreen_native->gbm.next_fb_id = 0; return; } /* If this is the first framebuffer to be presented then we now setup the * crtc modes, else we flip from the previous buffer */ if (onscreen_native->pending_set_crtc) { meta_onscreen_native_set_crtc_modes (onscreen_native); onscreen_native->pending_set_crtc = FALSE; } meta_onscreen_native_flip_crtcs (onscreen); } static gboolean meta_renderer_native_init_egl_context (CoglContext *cogl_context, GError **error) { COGL_FLAGS_SET (cogl_context->features, COGL_FEATURE_ID_SWAP_BUFFERS_EVENT, TRUE); /* TODO: remove this deprecated feature */ COGL_FLAGS_SET (cogl_context->winsys_features, COGL_WINSYS_FEATURE_SWAP_BUFFERS_EVENT, TRUE); COGL_FLAGS_SET (cogl_context->winsys_features, COGL_WINSYS_FEATURE_SYNC_AND_COMPLETE_EVENT, TRUE); COGL_FLAGS_SET (cogl_context->winsys_features, COGL_WINSYS_FEATURE_MULTIPLE_ONSCREEN, TRUE); return TRUE; } static gboolean meta_renderer_native_create_surface_gbm (MetaRendererNative *renderer_native, int width, int height, struct gbm_surface **gbm_surface, EGLSurface *egl_surface, GError **error) { MetaBackend *backend = meta_get_backend (); ClutterBackend *clutter_backend = meta_backend_get_clutter_backend (backend); CoglContext *cogl_context = clutter_backend_get_cogl_context (clutter_backend); CoglDisplay *cogl_display = cogl_context->display; CoglDisplayEGL *egl_display = cogl_display->winsys; CoglRendererEGL *egl_renderer = cogl_display->renderer->winsys; struct gbm_surface *new_gbm_surface; EGLNativeWindowType egl_native_window; EGLSurface new_egl_surface; new_gbm_surface = gbm_surface_create (renderer_native->gbm.device, width, height, GBM_FORMAT_XRGB8888, GBM_BO_USE_SCANOUT | GBM_BO_USE_RENDERING); 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 = eglCreateWindowSurface (egl_renderer->edpy, egl_display->egl_config, egl_native_window, NULL); if (new_egl_surface == EGL_NO_SURFACE) { gbm_surface_destroy (new_gbm_surface); g_set_error (error, COGL_WINSYS_ERROR, COGL_WINSYS_ERROR_CREATE_ONSCREEN, "Failed to allocate surface"); return FALSE; } *gbm_surface = new_gbm_surface; *egl_surface = new_egl_surface; return TRUE; } static gboolean meta_renderer_native_init_onscreen (CoglOnscreen *onscreen, GError **error) { CoglFramebuffer *framebuffer = COGL_FRAMEBUFFER (onscreen); CoglContext *cogl_context = framebuffer->context; CoglDisplay *cogl_display = cogl_context->display; CoglDisplayEGL *egl_display = cogl_display->winsys; CoglRenderer *cogl_renderer = cogl_display->renderer; CoglRendererEGL *egl_renderer = cogl_renderer->winsys; MetaRendererNative *renderer_native = egl_renderer->platform; CoglOnscreenEGL *egl_onscreen; MetaOnscreenNative *onscreen_native; struct gbm_surface *gbm_surface; EGLSurface egl_surface; int width; int height; _COGL_RETURN_VAL_IF_FAIL (egl_display->egl_context, FALSE); onscreen->winsys = g_slice_new0 (CoglOnscreenEGL); egl_onscreen = onscreen->winsys; onscreen_native = g_slice_new0 (MetaOnscreenNative); egl_onscreen->platform = onscreen_native; onscreen_native->pending_set_crtc = TRUE; /* 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; if (!meta_renderer_native_create_surface_gbm (renderer_native, width, height, &gbm_surface, &egl_surface, error)) return FALSE; onscreen_native->gbm.surface = gbm_surface; egl_onscreen->egl_surface = egl_surface; return TRUE; } static void meta_renderer_native_release_onscreen (CoglOnscreen *onscreen) { CoglFramebuffer *framebuffer = COGL_FRAMEBUFFER (onscreen); CoglContext *cogl_context = framebuffer->context; CoglRenderer *cogl_renderer = cogl_context->display->renderer; CoglRendererEGL *egl_renderer = cogl_renderer->winsys; CoglOnscreenEGL *egl_onscreen = onscreen->winsys; MetaOnscreenNative *onscreen_native; /* If we never successfully allocated then there's nothing to do */ if (egl_onscreen == NULL) return; onscreen_native = egl_onscreen->platform; /* 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_id == 0); free_current_bo (onscreen); if (egl_onscreen->egl_surface != EGL_NO_SURFACE) { eglDestroySurface (egl_renderer->edpy, egl_onscreen->egl_surface); egl_onscreen->egl_surface = EGL_NO_SURFACE; } if (onscreen_native->gbm.surface) { gbm_surface_destroy (onscreen_native->gbm.surface); onscreen_native->gbm.surface = NULL; } g_slice_free (MetaOnscreenNative, onscreen_native); g_slice_free (CoglOnscreenEGL, onscreen->winsys); onscreen->winsys = NULL; } static const CoglWinsysEGLVtable _cogl_winsys_egl_vtable = { .add_config_attributes = meta_renderer_native_add_egl_config_attributes, .display_setup = meta_renderer_native_setup_egl_display, .display_destroy = meta_renderer_native_destroy_egl_display, .context_created = meta_renderer_native_egl_context_created, .cleanup_context = meta_renderer_native_egl_cleanup_context, .context_init = meta_renderer_native_init_egl_context }; struct gbm_device * meta_renderer_native_get_gbm (MetaRendererNative *renderer_native) { return renderer_native->gbm.device; } int meta_renderer_native_get_kms_fd (MetaRendererNative *renderer_native) { return renderer_native->kms_fd; } void meta_renderer_native_queue_modes_reset (MetaRendererNative *renderer_native) { MetaRenderer *renderer = META_RENDERER (renderer_native); GList *l; for (l = meta_renderer_get_views (renderer); l; l = l->next) { ClutterStageView *stage_view = l->data; CoglFramebuffer *framebuffer = clutter_stage_view_get_onscreen (stage_view); CoglOnscreen *onscreen = COGL_ONSCREEN (framebuffer); CoglOnscreenEGL *egl_onscreen = onscreen->winsys; MetaOnscreenNative *onscreen_native = egl_onscreen->platform; onscreen_native->pending_set_crtc = TRUE; } } static MetaMonitorTransform meta_renderer_native_get_monitor_info_transform (MetaRenderer *renderer, MetaMonitorInfo *monitor_info) { MetaBackend *backend = meta_get_backend (); MetaMonitorManager *monitor_manager = meta_backend_get_monitor_manager (backend); MetaMonitorManagerKms *monitor_manager_kms = META_MONITOR_MANAGER_KMS (monitor_manager); g_assert (monitor_info->n_outputs > 0); return meta_monitor_manager_kms_get_view_transform (monitor_manager_kms, monitor_info->outputs[0]->crtc); } static CoglOnscreen * meta_renderer_native_create_onscreen (MetaRendererNative *renderer, CoglContext *context, MetaMonitorTransform transform, gint view_width, gint view_height) { CoglOnscreen *onscreen; gint width, height; GError *error = NULL; if (meta_monitor_transform_is_rotated (transform)) { width = view_height; height = view_width; } else { width = view_width; height = view_height; } onscreen = cogl_onscreen_new (context, width, height); cogl_onscreen_set_swap_throttled (onscreen, _clutter_get_sync_to_vblank ()); if (!cogl_framebuffer_allocate (COGL_FRAMEBUFFER (onscreen), &error)) { g_warning ("Could not create onscreen: %s", error->message); cogl_object_unref (onscreen); g_error_free (error); return NULL; } return onscreen; } static CoglOffscreen * meta_renderer_native_create_offscreen (MetaRendererNative *renderer, CoglContext *context, MetaMonitorTransform transform, gint view_width, gint view_height) { CoglOffscreen *fb; CoglTexture2D *tex; GError *error = NULL; tex = cogl_texture_2d_new_with_size (context, view_width, view_height); cogl_primitive_texture_set_auto_mipmap (COGL_PRIMITIVE_TEXTURE (tex), FALSE); if (!cogl_texture_allocate (COGL_TEXTURE (tex), &error)) { cogl_object_unref (tex); return FALSE; } fb = cogl_offscreen_new_with_texture (COGL_TEXTURE (tex)); cogl_object_unref (tex); if (!cogl_framebuffer_allocate (COGL_FRAMEBUFFER (fb), &error)) { g_warning ("Could not create offscreen: %s", error->message); g_error_free (error); cogl_object_unref (fb); return FALSE; } return fb; } gboolean meta_renderer_native_set_legacy_view_size (MetaRendererNative *renderer_native, MetaRendererView *view, int width, int height, GError **error) { ClutterBackend *clutter_backend = clutter_get_default_backend (); CoglContext *cogl_context = clutter_backend_get_cogl_context (clutter_backend); CoglDisplay *cogl_display = cogl_context_get_display (cogl_context); CoglRendererEGL *egl_renderer = cogl_display->renderer->winsys; ClutterStageView *stage_view = CLUTTER_STAGE_VIEW (view); cairo_rectangle_int_t view_layout; clutter_stage_view_get_layout (stage_view, &view_layout); if (width != view_layout.width || height != view_layout.height) { MetaBackend *backend = meta_get_backend (); MetaMonitorManager *monitor_manager = meta_backend_get_monitor_manager (backend); MetaMonitorManagerKms *monitor_manager_kms = META_MONITOR_MANAGER_KMS (monitor_manager); CoglFramebuffer *framebuffer = clutter_stage_view_get_onscreen (stage_view); CoglOnscreen *onscreen = COGL_ONSCREEN (framebuffer); CoglOnscreenEGL *egl_onscreen = onscreen->winsys; MetaOnscreenNative *onscreen_native = egl_onscreen->platform; CoglDisplayEGL *egl_display = cogl_display->winsys; struct gbm_surface *new_surface; EGLSurface new_egl_surface; cairo_rectangle_int_t view_layout; /* * Ensure we don't have any pending flips that will want * to swap the current buffer. */ while (onscreen_native->gbm.next_fb_id != 0) meta_monitor_manager_kms_wait_for_flip (monitor_manager_kms); /* Need to drop the GBM surface and create a new one */ if (!meta_renderer_native_create_surface_gbm (renderer_native, width, height, &new_surface, &new_egl_surface, error)) return FALSE; if (egl_onscreen->egl_surface) { _cogl_winsys_egl_make_current (cogl_display, egl_display->dummy_surface, egl_display->dummy_surface, egl_display->egl_context); eglDestroySurface (egl_renderer->edpy, egl_onscreen->egl_surface); } /* * Release the current buffer and destroy the associated surface. The * kernel will deal with keeping the actual buffer alive until its no * longer used. */ free_current_bo (onscreen); g_clear_pointer (&onscreen_native->gbm.surface, gbm_surface_destroy); /* * Update the active gbm and egl surfaces and make sure they they are * used for drawing the coming frame. */ onscreen_native->gbm.surface = new_surface; egl_onscreen->egl_surface = new_egl_surface; _cogl_winsys_egl_make_current (cogl_display, egl_onscreen->egl_surface, egl_onscreen->egl_surface, egl_display->egl_context); view_layout = (cairo_rectangle_int_t) { .width = width, .height = height }; g_object_set (G_OBJECT (view), "layout", &view_layout, NULL); _cogl_framebuffer_winsys_update_size (framebuffer, width, height); } meta_renderer_native_queue_modes_reset (renderer_native); return TRUE; } static const CoglWinsysVtable * get_native_cogl_winsys_vtable (void) { static gboolean vtable_inited = FALSE; static CoglWinsysVtable vtable; if (!vtable_inited) { /* The this winsys is a subclass of the EGL winsys so we start by copying its vtable */ parent_vtable = _cogl_winsys_egl_get_vtable (); vtable = *parent_vtable; vtable.id = COGL_WINSYS_ID_CUSTOM; vtable.name = "EGL_KMS"; vtable.renderer_connect = meta_renderer_native_connect; vtable.renderer_disconnect = meta_renderer_native_disconnect; vtable.onscreen_init = meta_renderer_native_init_onscreen; vtable.onscreen_deinit = meta_renderer_native_release_onscreen; /* The KMS winsys doesn't support swap region */ vtable.onscreen_swap_region = NULL; vtable.onscreen_swap_buffers_with_damage = meta_onscreen_native_swap_buffers_with_damage; vtable_inited = TRUE; } return &vtable; } static CoglRenderer * meta_renderer_native_create_cogl_renderer (MetaRenderer *renderer) { CoglRenderer *cogl_renderer; cogl_renderer = cogl_renderer_new (); cogl_renderer_set_custom_winsys (cogl_renderer, get_native_cogl_winsys_vtable); return cogl_renderer; } static void meta_onscreen_native_set_view (CoglOnscreen *onscreen, MetaRendererView *view) { CoglOnscreenEGL *egl_onscreen; MetaOnscreenNative *onscreen_native; egl_onscreen = onscreen->winsys; onscreen_native = egl_onscreen->platform; onscreen_native->view = view; } MetaRendererView * meta_renderer_native_create_legacy_view (MetaRendererNative *renderer_native) { MetaBackend *backend = meta_get_backend (); MetaMonitorManager *monitor_manager = meta_backend_get_monitor_manager (backend); CoglOnscreen *onscreen = NULL; ClutterBackend *clutter_backend = meta_backend_get_clutter_backend (backend); CoglContext *cogl_context = clutter_backend_get_cogl_context (clutter_backend); cairo_rectangle_int_t view_layout = { 0 }; MetaRendererView *view; if (!monitor_manager) return NULL; meta_monitor_manager_get_screen_size (monitor_manager, &view_layout.width, &view_layout.height); onscreen = meta_renderer_native_create_onscreen (renderer_native, cogl_context, META_MONITOR_TRANSFORM_NORMAL, view_layout.width, view_layout.height); if (!onscreen) meta_fatal ("Failed to allocate onscreen framebuffer\n"); view = g_object_new (META_TYPE_RENDERER_VIEW, "layout", &view_layout, "framebuffer", onscreen, NULL); cogl_object_unref (onscreen); meta_onscreen_native_set_view (onscreen, view); return view; } static MetaRendererView * meta_renderer_native_create_view (MetaRenderer *renderer, MetaMonitorInfo *monitor_info) { MetaBackend *backend = meta_get_backend (); ClutterBackend *clutter_backend = meta_backend_get_clutter_backend (backend); CoglContext *cogl_context = clutter_backend_get_cogl_context (clutter_backend); CoglDisplay *cogl_display = cogl_context_get_display (cogl_context); CoglDisplayEGL *egl_display = cogl_display->winsys; CoglOnscreenEGL *egl_onscreen; MetaMonitorTransform transform; CoglOnscreen *onscreen = NULL; CoglOffscreen *offscreen = NULL; MetaRendererView *view; transform = meta_renderer_native_get_monitor_info_transform (renderer, monitor_info); onscreen = meta_renderer_native_create_onscreen (META_RENDERER_NATIVE (renderer), cogl_context, transform, monitor_info->rect.width, monitor_info->rect.height); if (!onscreen) meta_fatal ("Failed to allocate onscreen framebuffer\n"); /* Ensure we don't point to stale surfaces when creating the offscreen */ egl_onscreen = onscreen->winsys; _cogl_winsys_egl_make_current (cogl_display, egl_onscreen->egl_surface, egl_onscreen->egl_surface, egl_display->egl_context); if (transform != META_MONITOR_TRANSFORM_NORMAL) { offscreen = meta_renderer_native_create_offscreen (META_RENDERER_NATIVE (renderer), cogl_context, transform, monitor_info->rect.width, monitor_info->rect.height); if (!offscreen) meta_fatal ("Failed to allocate back buffer texture\n"); } view = g_object_new (META_TYPE_RENDERER_VIEW, "layout", &monitor_info->rect, "framebuffer", onscreen, "offscreen", offscreen, "monitor-info", monitor_info, "transform", transform, NULL); cogl_object_unref (onscreen); g_clear_pointer (&offscreen, cogl_object_unref); meta_onscreen_native_set_view (onscreen, view); return view; } void meta_renderer_native_finish_frame (MetaRendererNative *renderer_native) { renderer_native->frame_counter++; } int64_t meta_renderer_native_get_frame_counter (MetaRendererNative *renderer_native) { return renderer_native->frame_counter; } static void meta_renderer_native_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { MetaRendererNative *renderer_native = META_RENDERER_NATIVE (object); switch (prop_id) { case PROP_KMS_FD: g_value_set_int (value, renderer_native->kms_fd); break; case PROP_KMS_FILE_PATH: g_value_set_string (value, renderer_native->kms_file_path); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void meta_renderer_native_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { MetaRendererNative *renderer_native = META_RENDERER_NATIVE (object); switch (prop_id) { case PROP_KMS_FD: renderer_native->kms_fd = g_value_get_int (value); break; case PROP_KMS_FILE_PATH: renderer_native->kms_file_path = g_strdup (g_value_get_string (value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void meta_renderer_native_finalize (GObject *object) { MetaRendererNative *renderer_native = META_RENDERER_NATIVE (object); g_clear_pointer (&renderer_native->gbm.device, gbm_device_destroy); g_free (renderer_native->kms_file_path); G_OBJECT_CLASS (meta_renderer_native_parent_class)->finalize (object); } static gboolean init_gbm (MetaRendererNative *renderer_native, GError **error) { MetaBackend *backend = meta_get_backend (); MetaEgl *egl = meta_backend_get_egl (backend); struct gbm_device *gbm_device; EGLDisplay egl_display; if (!meta_egl_has_extensions (egl, EGL_NO_DISPLAY, NULL, "EGL_MESA_platform_gbm", NULL)) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Missing extension for GBM renderer: EGL_KHR_platform_gbm"); return FALSE; } gbm_device = gbm_create_device (renderer_native->kms_fd); if (!gbm_device) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Failed to create gbm device: %s", g_strerror (errno)); return FALSE; } egl_display = meta_egl_get_platform_display (egl, EGL_PLATFORM_GBM_KHR, gbm_device, NULL, error); if (egl_display == EGL_NO_DISPLAY) { gbm_device_destroy (gbm_device); return FALSE; } renderer_native->egl_display = egl_display; renderer_native->gbm.device = gbm_device; return TRUE; } static gboolean meta_renderer_native_initable_init (GInitable *initable, GCancellable *cancellable, GError **error) { MetaRendererNative *renderer_native = META_RENDERER_NATIVE (initable); return init_gbm (renderer_native, error); } static void initable_iface_init (GInitableIface *initable_iface) { initable_iface->init = meta_renderer_native_initable_init; } static void meta_renderer_native_init (MetaRendererNative *renderer_native) { } static void meta_renderer_native_class_init (MetaRendererNativeClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); MetaRendererClass *renderer_class = META_RENDERER_CLASS (klass); object_class->get_property = meta_renderer_native_get_property; object_class->set_property = meta_renderer_native_set_property; object_class->finalize = meta_renderer_native_finalize; renderer_class->create_cogl_renderer = meta_renderer_native_create_cogl_renderer; renderer_class->create_view = meta_renderer_native_create_view; g_object_class_install_property (object_class, PROP_KMS_FD, g_param_spec_int ("kms-fd", "KMS fd", "The KMS file descriptor", 0, G_MAXINT, 0, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); g_object_class_install_property (object_class, PROP_KMS_FILE_PATH, g_param_spec_string ("kms-file-path", "KMS file path", "The KMS file path", NULL, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); } MetaRendererNative * meta_renderer_native_new (int kms_fd, const char *kms_file_path, GError **error) { MetaRendererNative *renderer_native; renderer_native = g_object_new (META_TYPE_RENDERER_NATIVE, "kms-fd", kms_fd, "kms-file-path", kms_file_path, NULL); if (!g_initable_init (G_INITABLE (renderer_native), NULL, error)) { g_object_unref (renderer_native); return NULL; } return renderer_native; }