mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
298 lines
11 KiB
C++
298 lines
11 KiB
C++
// Copyright 2013 The Flutter Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style license that can be
|
|
// found in the LICENSE file.
|
|
|
|
#define WL_EGL_PLATFORM
|
|
|
|
#include "fl_renderer_wayland.h"
|
|
#include "flutter/shell/platform/linux/egl_utils.h"
|
|
|
|
#include <gdk/gdkwayland.h>
|
|
#include <wayland-egl-core.h>
|
|
#include <cstring>
|
|
|
|
struct _FlRendererWayland {
|
|
FlRenderer parent_instance;
|
|
wl_registry* registry;
|
|
wl_subcompositor* subcompositor;
|
|
|
|
struct {
|
|
wl_subsurface* subsurface;
|
|
wl_surface* surface;
|
|
wl_egl_window* egl_window;
|
|
GdkRectangle geometry;
|
|
gint scale;
|
|
} subsurface;
|
|
|
|
// The resource surface will not be mapped, but needs to be a wl_surface
|
|
// because ONLY window EGL surfaces are supported on Wayland.
|
|
struct {
|
|
wl_surface* surface;
|
|
wl_egl_window* egl_window;
|
|
} resource;
|
|
};
|
|
|
|
G_DEFINE_TYPE(FlRendererWayland, fl_renderer_wayland, fl_renderer_get_type())
|
|
|
|
// wl_registry.global callback.
|
|
static void registry_handle_global(void* data,
|
|
wl_registry* registry,
|
|
uint32_t id,
|
|
const char* name,
|
|
uint32_t max_version) {
|
|
FlRendererWayland* self = FL_RENDERER_WAYLAND(data);
|
|
if (strcmp(name, wl_subcompositor_interface.name) == 0) {
|
|
uint32_t version = MIN(
|
|
static_cast<uint32_t>(wl_subcompositor_interface.version), max_version);
|
|
self->subcompositor = static_cast<wl_subcompositor*>(
|
|
wl_registry_bind(registry, id, &wl_subcompositor_interface, version));
|
|
}
|
|
}
|
|
|
|
// wl_registry.global_remove callback.
|
|
// Can be safely ignored unless we bind to globals that might be removed.
|
|
static void registry_handle_global_remove(void*, wl_registry*, uint32_t) {}
|
|
|
|
static const wl_registry_listener registry_listener = {
|
|
.global = registry_handle_global,
|
|
.global_remove = registry_handle_global_remove,
|
|
};
|
|
|
|
// The first time this function is called, all Wayland globals are initialized
|
|
// (which blocks for a round trip to the Wayland compositor).
|
|
// Subsequent calls return immediately.
|
|
static void fl_renderer_wayland_lazy_init_wl(FlRendererWayland* self) {
|
|
if (self->registry) {
|
|
return;
|
|
}
|
|
|
|
GdkWaylandDisplay* gdk_display =
|
|
GDK_WAYLAND_DISPLAY(gdk_display_get_default());
|
|
g_return_if_fail(gdk_display);
|
|
|
|
wl_display* display = gdk_wayland_display_get_wl_display(gdk_display);
|
|
self->registry = wl_display_get_registry(display);
|
|
wl_registry_add_listener(self->registry, ®istry_listener, self);
|
|
wl_display_roundtrip(display);
|
|
}
|
|
|
|
// Implements GObject::dispose.
|
|
static void fl_renderer_wayland_dispose(GObject* object) {
|
|
FlRendererWayland* self = FL_RENDERER_WAYLAND(object);
|
|
|
|
g_clear_pointer(&self->registry, wl_registry_destroy);
|
|
g_clear_pointer(&self->subcompositor, wl_subcompositor_destroy);
|
|
|
|
g_clear_pointer(&self->subsurface.subsurface, wl_subsurface_destroy);
|
|
g_clear_pointer(&self->subsurface.egl_window, wl_egl_window_destroy);
|
|
g_clear_pointer(&self->subsurface.surface, wl_surface_destroy);
|
|
|
|
g_clear_pointer(&self->resource.egl_window, wl_egl_window_destroy);
|
|
g_clear_pointer(&self->resource.surface, wl_surface_destroy);
|
|
|
|
G_OBJECT_CLASS(fl_renderer_wayland_parent_class)->dispose(object);
|
|
}
|
|
|
|
// Implements FlRenderer::create_display.
|
|
static EGLDisplay fl_renderer_wayland_create_display(FlRenderer* /*renderer*/) {
|
|
GdkWaylandDisplay* gdk_display =
|
|
GDK_WAYLAND_DISPLAY(gdk_display_get_default());
|
|
g_return_val_if_fail(gdk_display, nullptr);
|
|
return eglGetDisplay(gdk_wayland_display_get_wl_display(gdk_display));
|
|
}
|
|
|
|
static void fl_renderer_wayland_on_window_map(FlRendererWayland* self,
|
|
GtkWidget* widget) {
|
|
if (self->subsurface.subsurface) {
|
|
g_error("fl_renderer_wayland_on_window_map: already has a subsurface");
|
|
return;
|
|
}
|
|
|
|
GdkWaylandDisplay* gdk_display =
|
|
GDK_WAYLAND_DISPLAY(gdk_display_get_default());
|
|
|
|
wl_compositor* compositor =
|
|
gdk_wayland_display_get_wl_compositor(gdk_display);
|
|
|
|
fl_renderer_wayland_lazy_init_wl(self);
|
|
if (!self->subcompositor) {
|
|
g_error(
|
|
"fl_renderer_wayland_on_window_map: could not bind to "
|
|
"wl_subcompositor");
|
|
return;
|
|
}
|
|
|
|
GdkWaylandWindow* window = GDK_WAYLAND_WINDOW(gtk_widget_get_window(widget));
|
|
if (!window) {
|
|
g_error("fl_renderer_wayland_on_window_map: not a Wayland window");
|
|
return;
|
|
}
|
|
wl_surface* toplevel_surface = gdk_wayland_window_get_wl_surface(window);
|
|
if (!toplevel_surface) {
|
|
g_error("fl_renderer_wayland_on_window_map: could not get wl_surface");
|
|
return;
|
|
}
|
|
|
|
self->subsurface.subsurface = wl_subcompositor_get_subsurface(
|
|
self->subcompositor, self->subsurface.surface, toplevel_surface);
|
|
if (!self->subsurface.subsurface) {
|
|
g_error("fl_renderer_wayland_on_window_map: could not create subsurface");
|
|
return;
|
|
}
|
|
|
|
wl_subsurface_set_desync(self->subsurface.subsurface);
|
|
wl_subsurface_set_position(self->subsurface.subsurface,
|
|
self->subsurface.geometry.x,
|
|
self->subsurface.geometry.y);
|
|
|
|
// Give the subsurface an empty input region so the main surface gets input.
|
|
wl_region* region = wl_compositor_create_region(compositor);
|
|
wl_surface_set_input_region(self->subsurface.surface, region);
|
|
wl_region_destroy(region);
|
|
|
|
wl_surface_commit(self->subsurface.surface);
|
|
}
|
|
|
|
static void fl_renderer_wayland_on_window_unmap(FlRendererWayland* self,
|
|
GtkWidget* widget) {
|
|
g_clear_pointer(&self->subsurface.subsurface, wl_subsurface_destroy);
|
|
}
|
|
|
|
// Implements FlRenderer::create_surfaces.
|
|
static gboolean fl_renderer_wayland_create_surfaces(FlRenderer* renderer,
|
|
GtkWidget* widget,
|
|
EGLDisplay display,
|
|
EGLConfig config,
|
|
EGLSurface* visible,
|
|
EGLSurface* resource,
|
|
GError** error) {
|
|
FlRendererWayland* self = FL_RENDERER_WAYLAND(renderer);
|
|
|
|
if (self->subsurface.surface || self->subsurface.egl_window ||
|
|
self->resource.surface || self->resource.egl_window) {
|
|
g_set_error(error, fl_renderer_error_quark(), FL_RENDERER_ERROR_FAILED,
|
|
"Surfaces already created");
|
|
return FALSE;
|
|
}
|
|
|
|
if (!GDK_IS_WAYLAND_DISPLAY(gdk_display_get_default())) {
|
|
g_set_error(error, fl_renderer_error_quark(), FL_RENDERER_ERROR_FAILED,
|
|
"Expected Wayland display");
|
|
return FALSE;
|
|
}
|
|
GdkWaylandDisplay* gdk_display =
|
|
GDK_WAYLAND_DISPLAY(gdk_display_get_default());
|
|
|
|
wl_compositor* compositor =
|
|
gdk_wayland_display_get_wl_compositor(gdk_display);
|
|
if (!compositor) {
|
|
g_set_error(error, fl_renderer_error_quark(), FL_RENDERER_ERROR_FAILED,
|
|
"No wl_compositor");
|
|
return FALSE;
|
|
}
|
|
|
|
// Make sure size and scale is not <= 0
|
|
self->subsurface.scale = MAX(self->subsurface.scale, 1);
|
|
self->subsurface.geometry.width = MAX(self->subsurface.geometry.width, 1);
|
|
self->subsurface.geometry.height = MAX(self->subsurface.geometry.height, 1);
|
|
|
|
self->subsurface.surface = wl_compositor_create_surface(compositor);
|
|
self->resource.surface = wl_compositor_create_surface(compositor);
|
|
if (!self->subsurface.surface || !self->resource.surface) {
|
|
g_set_error(error, fl_renderer_error_quark(), FL_RENDERER_ERROR_FAILED,
|
|
"Failed to create wl_surfaces");
|
|
return FALSE;
|
|
}
|
|
wl_surface_set_buffer_scale(self->subsurface.surface, self->subsurface.scale);
|
|
|
|
gint window_width = self->subsurface.geometry.width * self->subsurface.scale;
|
|
gint window_height =
|
|
self->subsurface.geometry.height * self->subsurface.scale;
|
|
self->subsurface.egl_window = wl_egl_window_create(
|
|
self->subsurface.surface, window_width, window_height);
|
|
self->resource.egl_window =
|
|
wl_egl_window_create(self->resource.surface, 1, 1);
|
|
if (!self->subsurface.egl_window || !self->resource.egl_window) {
|
|
g_set_error(error, fl_renderer_error_quark(), FL_RENDERER_ERROR_FAILED,
|
|
"Failed to create EGL windows");
|
|
return FALSE;
|
|
}
|
|
|
|
*visible = eglCreateWindowSurface(display, config,
|
|
self->subsurface.egl_window, nullptr);
|
|
*resource = eglCreateWindowSurface(display, config, self->resource.egl_window,
|
|
nullptr);
|
|
if (*visible == EGL_NO_SURFACE || *resource == EGL_NO_SURFACE) {
|
|
EGLint egl_error = eglGetError(); // must be before egl_config_to_string()
|
|
g_autofree gchar* config_string = egl_config_to_string(display, config);
|
|
g_set_error(error, fl_renderer_error_quark(), FL_RENDERER_ERROR_FAILED,
|
|
"Failed to create EGL surfaces using configuration (%s): %s",
|
|
config_string, egl_error_to_string(egl_error));
|
|
return FALSE;
|
|
}
|
|
|
|
GtkWidget* toplevel = gtk_widget_get_toplevel(widget);
|
|
if (!toplevel) {
|
|
g_set_error(error, fl_renderer_error_quark(), FL_RENDERER_ERROR_FAILED,
|
|
"Renderer does not have a widget");
|
|
return FALSE;
|
|
}
|
|
g_signal_connect_object(toplevel, "map",
|
|
G_CALLBACK(fl_renderer_wayland_on_window_map), self,
|
|
G_CONNECT_SWAPPED);
|
|
g_signal_connect_object(toplevel, "unmap",
|
|
G_CALLBACK(fl_renderer_wayland_on_window_unmap), self,
|
|
G_CONNECT_SWAPPED);
|
|
if (gtk_widget_get_mapped(toplevel)) {
|
|
fl_renderer_wayland_on_window_map(self, toplevel);
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
// Implements FlRenderer::set_geometry.
|
|
static void fl_renderer_wayland_set_geometry(FlRenderer* renderer,
|
|
GdkRectangle* geometry,
|
|
gint scale) {
|
|
FlRendererWayland* self = FL_RENDERER_WAYLAND(renderer);
|
|
|
|
if (scale != self->subsurface.scale && self->subsurface.surface) {
|
|
wl_surface_set_buffer_scale(self->subsurface.surface, scale);
|
|
}
|
|
|
|
// NOTE: position is unscaled but size is scaled.
|
|
|
|
if ((geometry->x != self->subsurface.geometry.x ||
|
|
geometry->y != self->subsurface.geometry.y) &&
|
|
self->subsurface.subsurface) {
|
|
wl_subsurface_set_position(self->subsurface.subsurface, geometry->x,
|
|
geometry->y);
|
|
}
|
|
|
|
if ((geometry->width != self->subsurface.geometry.width ||
|
|
geometry->height != self->subsurface.geometry.height ||
|
|
scale != self->subsurface.scale) &&
|
|
self->subsurface.egl_window) {
|
|
wl_egl_window_resize(self->subsurface.egl_window, geometry->width * scale,
|
|
geometry->height * scale, 0, 0);
|
|
}
|
|
|
|
self->subsurface.geometry = *geometry;
|
|
self->subsurface.scale = scale;
|
|
}
|
|
|
|
static void fl_renderer_wayland_class_init(FlRendererWaylandClass* klass) {
|
|
G_OBJECT_CLASS(klass)->dispose = fl_renderer_wayland_dispose;
|
|
FL_RENDERER_CLASS(klass)->create_display = fl_renderer_wayland_create_display;
|
|
FL_RENDERER_CLASS(klass)->create_surfaces =
|
|
fl_renderer_wayland_create_surfaces;
|
|
FL_RENDERER_CLASS(klass)->set_geometry = fl_renderer_wayland_set_geometry;
|
|
}
|
|
|
|
static void fl_renderer_wayland_init(FlRendererWayland* self) {}
|
|
|
|
FlRendererWayland* fl_renderer_wayland_new() {
|
|
return FL_RENDERER_WAYLAND(
|
|
g_object_new(fl_renderer_wayland_get_type(), nullptr));
|
|
}
|