implement minimal Wayland server to import EGL surfaces into GtkGraphicsOffload

This allows us to use the translucent cutout feature of
GtkGraphicsOffload, which can not be implemented with our custom
subsurface implementation.
This commit is contained in:
Julian Winkler 2024-11-10 18:07:31 +01:00
parent c5ed3c6b9c
commit 58f29b7ba2
5 changed files with 312 additions and 2 deletions

View file

@ -9,3 +9,4 @@ this is useful for saving screen space on phone screens, as well as working arou
`ATL_UGLY_ENABLE_WEBVIEW=` - if not set, WebView will be stubbed as a generic View; this will avoid wasting resources on WebViews which are only used for fingerprinting and ads `ATL_UGLY_ENABLE_WEBVIEW=` - if not set, WebView will be stubbed as a generic View; this will avoid wasting resources on WebViews which are only used for fingerprinting and ads
`ATL_FORCE_FULLSCREEN` - if set, will fullscreen the app window on start; this is useful for saving screen space on phone screens, as well as making apps that can't handle arbitrary screen dimensions for some reason happier `ATL_FORCE_FULLSCREEN` - if set, will fullscreen the app window on start; this is useful for saving screen space on phone screens, as well as making apps that can't handle arbitrary screen dimensions for some reason happier
`ATL_SKIP_NATIVES_EXTRACTION` - if set, natives will not be extracted automatically; it's already possible to replace a native lib, but removing it entirely will normally result in it getting re-extracted, which may not be what you want `ATL_SKIP_NATIVES_EXTRACTION` - if set, natives will not be extracted automatically; it's already possible to replace a native lib, but removing it entirely will normally result in it getting re-extracted, which may not be what you want
`ATL_DIRECT_EGL` - if set, SurfaceViews will be mapped directly to a Wayland subsurface or X11 window instead of using GtkGraphicsOffload. This might be beneficial for CPU usage and rendering latency, but does not allow the application to render other Views ontop of the SurfaceView.

View file

@ -65,6 +65,7 @@ libandroid_so = shared_library('android', [
'src/libandroid/native_window.c', 'src/libandroid/native_window.c',
'src/libandroid/sensor.c', 'src/libandroid/sensor.c',
'src/libandroid/trace.c', 'src/libandroid/trace.c',
'src/libandroid/wayland_server.c',
], ],
install: true, install: true,
soversion: 0, soversion: 0,

View file

@ -56,6 +56,7 @@
#include "../api-impl-jni/defines.h" #include "../api-impl-jni/defines.h"
#include "native_window.h" #include "native_window.h"
#include "wayland_server.h"
/** /**
* Transforms that can be applied to buffers as they are displayed to a window. * Transforms that can be applied to buffers as they are displayed to a window.
@ -233,6 +234,14 @@ void wl_registry_global_handler(void *data, struct wl_registry *registry, uint32
} }
} }
void wl_registry_global_handler_compositor(void *data, struct wl_registry *registry, uint32_t name, const char *interface, uint32_t version)
{
struct wl_subcompositor **compositor = data;
if (!strcmp(interface,"wl_compositor")) {
*compositor = wl_registry_bind(registry, name, &wl_compositor_interface, 1);
}
}
void wl_registry_global_remove_handler(void *data, struct wl_registry *registry, uint32_t name) void wl_registry_global_remove_handler(void *data, struct wl_registry *registry, uint32_t name)
{ {
printf("removed: %u\n", name); printf("removed: %u\n", name);
@ -249,6 +258,8 @@ static void on_resize(GtkWidget* self, gint width, gint height, EGLNativeWindowT
} }
} }
static struct wl_display *wl_display_client = NULL;
extern GThread *main_thread_id; extern GThread *main_thread_id;
ANativeWindow * ANativeWindow_fromSurface(JNIEnv* env, jobject surface) ANativeWindow * ANativeWindow_fromSurface(JNIEnv* env, jobject surface)
{ {
@ -266,6 +277,11 @@ ANativeWindow * ANativeWindow_fromSurface(JNIEnv* env, jobject surface)
.global = wl_registry_global_handler, .global = wl_registry_global_handler,
.global_remove = wl_registry_global_remove_handler .global_remove = wl_registry_global_remove_handler
}; };
static struct wl_compositor *wl_compositor = NULL;
static struct wl_registry_listener wl_registry_listener_compositor = {
.global = wl_registry_global_handler_compositor,
.global_remove = wl_registry_global_remove_handler
};
GtkWidget *surface_view_widget = _PTR(_GET_LONG_FIELD(surface, "widget")); GtkWidget *surface_view_widget = _PTR(_GET_LONG_FIELD(surface, "widget"));
GtkWidget *window = GTK_WIDGET(gtk_widget_get_native(surface_view_widget)); GtkWidget *window = GTK_WIDGET(gtk_widget_get_native(surface_view_widget));
@ -295,7 +311,33 @@ ANativeWindow * ANativeWindow_fromSurface(JNIEnv* env, jobject surface)
GdkDisplay *display = gtk_root_get_display(GTK_ROOT(window)); GdkDisplay *display = gtk_root_get_display(GTK_ROOT(window));
if (GDK_IS_WAYLAND_DISPLAY (display)) { if (!getenv("ATL_DIRECT_EGL")) {
GtkWidget *graphics_offload = gtk_widget_get_first_child(surface_view_widget);
if (!GTK_IS_GRAPHICS_OFFLOAD(graphics_offload)) {
graphics_offload = gtk_graphics_offload_new(gtk_picture_new());
gtk_widget_insert_after(graphics_offload, surface_view_widget, NULL);
}
GtkPicture *gtk_picture = GTK_PICTURE(gtk_graphics_offload_get_child(GTK_GRAPHICS_OFFLOAD(graphics_offload)));
if (!wl_compositor) {
if (!wl_display_client)
wl_display_client = wayland_server_start();
struct wl_registry *registry = wl_display_get_registry(wl_display_client);
wl_registry_add_listener(registry, &wl_registry_listener_compositor, &wl_compositor);
wl_display_roundtrip(wl_display_client);
printf("XXX: wl_compositor: %p\n", wl_compositor);
}
struct wl_surface *wayland_surface = wl_compositor_create_surface(wl_compositor);
// transfer the GtkPicture pointer to the wayland server abusing the set_buffer_scale and set_buffer_transform methods
g_object_ref(gtk_picture);
wl_surface_set_buffer_scale(wayland_surface, _INTPTR(gtk_picture));
wl_surface_set_buffer_transform(wayland_surface, _INTPTR(gtk_picture)>>32);
struct wl_egl_window *egl_window = wl_egl_window_create(wayland_surface, width, height);
native_window->egl_window = (EGLNativeWindowType)egl_window;
native_window->wayland_display = wl_display_client;
native_window->wayland_surface = wayland_surface;
printf("EGL::: wayland_surface: %p\n", wayland_surface);
} else if (GDK_IS_WAYLAND_DISPLAY (display)) {
struct wl_display *wl_display = gdk_wayland_display_get_wl_display(display); struct wl_display *wl_display = gdk_wayland_display_get_wl_display(display);
struct wl_compositor *wl_compositor = gdk_wayland_display_get_wl_compositor(display); struct wl_compositor *wl_compositor = gdk_wayland_display_get_wl_compositor(display);
@ -466,7 +508,13 @@ EGLDisplay bionic_eglGetDisplay(NativeDisplayType native_display)
* than the "default" display (especially on Wayland) * than the "default" display (especially on Wayland)
*/ */
GdkDisplay *display = gtk_root_get_display(GTK_ROOT(window)); GdkDisplay *display = gtk_root_get_display(GTK_ROOT(window));
if (GDK_IS_WAYLAND_DISPLAY (display)) {
if (!getenv("ATL_DIRECT_EGL")) {
if (!wl_display_client)
wl_display_client = wayland_server_start();
EGLDisplay egl_display = eglGetPlatformDisplay(EGL_PLATFORM_WAYLAND_KHR, wl_display_client, NULL);
return egl_display;
} else if (GDK_IS_WAYLAND_DISPLAY (display)) {
struct wl_display *wl_display = gdk_wayland_display_get_wl_display(display); struct wl_display *wl_display = gdk_wayland_display_get_wl_display(display);
return eglGetPlatformDisplay(EGL_PLATFORM_WAYLAND_KHR, wl_display, NULL); return eglGetPlatformDisplay(EGL_PLATFORM_WAYLAND_KHR, wl_display, NULL);
} else if (GDK_IS_X11_DISPLAY (display)) { } else if (GDK_IS_X11_DISPLAY (display)) {

View file

@ -0,0 +1,257 @@
#include <sys/poll.h>
#include <wayland-server.h>
#include <GL/gl.h>
#include <EGL/egl.h>
#include <EGL/eglext.h>
#include <gtk/gtk.h>
#include <gdk/wayland/gdkwayland.h>
#include "../api-impl-jni/defines.h"
static EGLDisplay egl_display_gtk = NULL;
static GdkGLContext *gl_context_gtk = NULL;
static struct wl_event_loop *event_loop = NULL;
static GMutex mutex;
static PFNEGLQUERYWAYLANDBUFFERWL eglQueryWaylandBufferWL = NULL;
static PFNGLEGLIMAGETARGETTEXTURE2DOESPROC glEGLImageTargetTexture2DOES = NULL;
/* runs on main thread */
static gboolean delete_texture(void *data) {
GLuint texture_id = (uintptr_t)data;
gdk_gl_context_make_current(gl_context_gtk);
glDeleteTextures(1, &texture_id);
return G_SOURCE_REMOVE;
}
struct _BufferData {
GObject parent;
gboolean destroyed;
GtkPicture *picture;
struct wl_resource *wl_buffer;
GdkGLTextureBuilder *texture_builder;
};
G_DECLARE_FINAL_TYPE(BufferData, buffer_data, ATL, BUFFER_DATA, GObject);
static void buffer_data_dispose(GObject *g_object)
{
BufferData *buffer = ATL_BUFFER_DATA(g_object);
if (buffer->texture_builder) {
GLuint texture_id = gdk_gl_texture_builder_get_id(buffer->texture_builder);
g_idle_add(delete_texture, _PTR(texture_id));
g_object_unref(buffer->texture_builder);
}
g_object_unref(buffer->picture);
}
static void buffer_data_class_init(BufferDataClass *cls)
{
cls->parent_class.dispose = buffer_data_dispose;
}
static void buffer_data_init(BufferData *self) {}
G_DEFINE_TYPE(BufferData, buffer_data, G_TYPE_OBJECT)
struct surface {
struct wl_resource *wl_surface;
GtkPicture *picture;
struct wl_resource *frame_callback;
BufferData *buffers[3];
};
/* runs on main thread */
static void destroy_texture(void *data) {
BufferData *buffer = ATL_BUFFER_DATA(data);
g_mutex_lock(&mutex);
if (!buffer->destroyed) {
wl_buffer_send_release(buffer->wl_buffer);
}
g_mutex_unlock(&mutex);
g_object_unref(buffer);
}
/* runs on main thread */
static gboolean render_texture(void *data) {
BufferData *buffer = ATL_BUFFER_DATA(data);
if (!buffer->texture_builder) {
g_mutex_lock(&mutex);
if (buffer->destroyed) {
g_mutex_unlock(&mutex);
g_object_unref(buffer);
return G_SOURCE_REMOVE;
}
EGLImage image = eglCreateImage(egl_display_gtk, EGL_NO_CONTEXT, EGL_WAYLAND_BUFFER_WL, buffer->wl_buffer, (EGLAttrib[]){EGL_NONE});
GLuint texture_id;
int width, height;
eglQueryWaylandBufferWL(egl_display_gtk, buffer->wl_buffer, EGL_WIDTH, &width);
eglQueryWaylandBufferWL(egl_display_gtk, buffer->wl_buffer, EGL_HEIGHT, &height);
gdk_gl_context_make_current(gl_context_gtk);
glGenTextures(1, &texture_id);
glBindTexture(GL_TEXTURE_2D, texture_id);
glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, image);
glBindTexture(GL_TEXTURE_2D, 0);
eglDestroyImage(egl_display_gtk, image);
g_mutex_unlock(&mutex);
buffer->texture_builder = gdk_gl_texture_builder_new();
gdk_gl_texture_builder_set_context(buffer->texture_builder, gl_context_gtk);
gdk_gl_texture_builder_set_id(buffer->texture_builder, texture_id);
gdk_gl_texture_builder_set_format(buffer->texture_builder, GDK_MEMORY_R8G8B8A8_PREMULTIPLIED);
gdk_gl_texture_builder_set_width(buffer->texture_builder, width);
gdk_gl_texture_builder_set_height(buffer->texture_builder, height);
}
GdkTexture *texture = gdk_gl_texture_builder_build(buffer->texture_builder, destroy_texture, buffer);
gtk_picture_set_paintable(buffer->picture, GDK_PAINTABLE(texture));
g_object_unref(texture);
return G_SOURCE_REMOVE;
}
static void surface_attach(struct wl_client *client, struct wl_resource *resource, struct wl_resource *wl_buffer, int32_t x, int32_t y)
{
struct surface *surface = wl_resource_get_user_data(resource);
// Order buffer cache by least recently used.
BufferData *buffer = surface->buffers[0];
if (buffer && buffer->wl_buffer == wl_buffer)
return;
buffer = surface->buffers[1];
surface->buffers[1] = surface->buffers[0];
surface->buffers[0] = buffer;
if (buffer && buffer->wl_buffer == wl_buffer)
return;
buffer = surface->buffers[2];
surface->buffers[2] = surface->buffers[0];
surface->buffers[0] = buffer;
if (buffer && buffer->wl_buffer == wl_buffer)
return;
// If the buffer is not in the cache, create it and drop the oldest one.
if (buffer)
g_object_unref(buffer);
buffer = g_object_new(buffer_data_get_type(), NULL);
buffer->wl_buffer = wl_buffer;
buffer->picture = g_object_ref(surface->picture);
surface->buffers[0] = buffer;
}
static void surface_frame(struct wl_client *client, struct wl_resource *resource, uint32_t callback)
{
struct surface *surface = wl_resource_get_user_data(resource);
surface->frame_callback = wl_resource_create(client, &wl_callback_interface, 1, callback);
}
static void surface_damage(struct wl_client *client, struct wl_resource *resource, int32_t x, int32_t y, int32_t width, int32_t height)
{
}
static int frame_timer(void *data)
{
struct wl_resource *frame_callback = data;
wl_callback_send_done(frame_callback, g_get_monotonic_time()/1000);
wl_resource_destroy(frame_callback);
return 0;
}
static void surface_commit(struct wl_client *client, struct wl_resource *resource)
{
struct surface *surface = wl_resource_get_user_data(resource);
g_idle_add(render_texture, g_object_ref(surface->buffers[0]));
struct wl_event_source *timer = wl_event_loop_add_timer(event_loop, frame_timer, surface->frame_callback);
wl_event_source_timer_update(timer, 1000/60);
}
static void surface_destroy(struct wl_client *client, struct wl_resource *resource) {
struct surface *surface = wl_resource_get_user_data(resource);
for (int i = 0; i < 3; i++) if (surface->buffers[i]) {
surface->buffers[i]->destroyed = TRUE;
g_object_unref(surface->buffers[i]);
}
g_object_unref(surface->picture);
wl_resource_destroy(surface->wl_surface);
g_free(surface);
}
/* we abuse this method to set lower 32 bits of the GtkPicture pointer */
static void surface_set_buffer_scale(struct wl_client *client, struct wl_resource *resource, int32_t scale)
{
struct surface *surface = wl_resource_get_user_data(resource);
surface->picture = _PTR((((uint64_t)(uintptr_t)surface->picture) & 0xffffffff00000000L) | ((uintptr_t)(uint32_t)scale));
}
/* we abuse this method to set higher 32 bits of the GtkPicture pointer */
static void surface_set_buffer_transform(struct wl_client *client, struct wl_resource *resource, int32_t transform)
{
struct surface *surface = wl_resource_get_user_data(resource);
surface->picture = _PTR((((uint64_t)(uintptr_t)surface->picture) & 0x00000000ffffffffL) | ((uintptr_t)(uint32_t)transform)<<32);
}
static struct wl_surface_interface surface_implementation = {
.attach = surface_attach,
.frame = surface_frame,
.damage = surface_damage,
.commit = surface_commit,
.destroy = surface_destroy,
.set_buffer_scale = surface_set_buffer_scale,
.set_buffer_transform = surface_set_buffer_transform,
};
static void compositor_create_surface(struct wl_client *client, struct wl_resource *resource, uint32_t id)
{
struct wl_resource *wl_surface = wl_resource_create(client, &wl_surface_interface, 3, id);
struct surface *surface = g_new0(struct surface, 1);
wl_resource_set_implementation(wl_surface, &surface_implementation, surface, NULL);
surface->wl_surface = wl_surface;
}
static struct wl_compositor_interface compositor_implementation = {
.create_surface = compositor_create_surface,
};
static void compositor_bind(struct wl_client *client, void *data, uint32_t version, uint32_t id) {
struct wl_resource *resource = wl_resource_create(client, &wl_compositor_interface, 1, id);
wl_resource_set_implementation(resource, &compositor_implementation, NULL, NULL);
}
static gpointer wayland_server_thread(gpointer user_data)
{
struct wl_display *wl_display_server = user_data;
struct pollfd pollfd = {
.fd = wl_event_loop_get_fd(event_loop),
.events = POLLIN | POLLOUT,
};
for (;;) {
poll(&pollfd, 1, -1);
g_mutex_lock(&mutex);
wl_event_loop_dispatch(event_loop, 0);
wl_display_flush_clients(wl_display_server);
g_mutex_unlock(&mutex);
}
return 0;
}
extern GtkWindow *window;
struct wl_display *wayland_server_start()
{
GdkDisplay *gdk_display = gtk_root_get_display(GTK_ROOT(window));
struct wl_display *wl_display_gtk = gdk_wayland_display_get_wl_display(gdk_display);
egl_display_gtk = eglGetPlatformDisplay(EGL_PLATFORM_WAYLAND_KHR, wl_display_gtk, NULL);
gl_context_gtk = gdk_surface_create_gl_context(gtk_native_get_surface(GTK_NATIVE(window)), NULL);
struct wl_display *wl_display_server = wl_display_create();
char tmpname[] = "/tmp/tmpdir.XXXXXX\0socket";
int tmplen = strlen(tmpname);
mkdtemp(tmpname);
tmpname[tmplen] = '/';
wl_display_add_socket(wl_display_server, tmpname);
struct wl_display *wl_display_client = wl_display_connect(tmpname);
tmpname[tmplen] = '\0';
rmdir(tmpname);
wl_global_create(wl_display_server, &wl_compositor_interface, 3, NULL, &compositor_bind);
event_loop = wl_display_get_event_loop(wl_display_server);
g_mutex_init(&mutex);
PFNEGLBINDWAYLANDDISPLAYWL eglBindWaylandDisplayWL = (PFNEGLBINDWAYLANDDISPLAYWL)eglGetProcAddress("eglBindWaylandDisplayWL");
eglQueryWaylandBufferWL = (PFNEGLQUERYWAYLANDBUFFERWL)eglGetProcAddress("eglQueryWaylandBufferWL");
glEGLImageTargetTexture2DOES = (PFNGLEGLIMAGETARGETTEXTURE2DOESPROC)eglGetProcAddress("glEGLImageTargetTexture2DOES");
eglBindWaylandDisplayWL(egl_display_gtk, wl_display_server);
g_thread_new("wayland-server", wayland_server_thread, wl_display_server);
return wl_display_client;
}

View file

@ -0,0 +1,3 @@
struct wl_display;
struct wl_display *wayland_server_start(void);