diff --git a/doc/Envs.md b/doc/Envs.md index 34661961..1c89195c 100644 --- a/doc/Envs.md +++ b/doc/Envs.md @@ -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_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_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. diff --git a/meson.build b/meson.build index dbc18c1c..5f51d79f 100644 --- a/meson.build +++ b/meson.build @@ -65,6 +65,7 @@ libandroid_so = shared_library('android', [ 'src/libandroid/native_window.c', 'src/libandroid/sensor.c', 'src/libandroid/trace.c', + 'src/libandroid/wayland_server.c', ], install: true, soversion: 0, diff --git a/src/libandroid/native_window.c b/src/libandroid/native_window.c index f3d55dd9..7a813726 100644 --- a/src/libandroid/native_window.c +++ b/src/libandroid/native_window.c @@ -56,6 +56,7 @@ #include "../api-impl-jni/defines.h" #include "native_window.h" +#include "wayland_server.h" /** * 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) { 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; ANativeWindow * ANativeWindow_fromSurface(JNIEnv* env, jobject surface) { @@ -266,6 +277,11 @@ ANativeWindow * ANativeWindow_fromSurface(JNIEnv* env, jobject surface) .global = wl_registry_global_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 *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)); - 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_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) */ 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); return eglGetPlatformDisplay(EGL_PLATFORM_WAYLAND_KHR, wl_display, NULL); } else if (GDK_IS_X11_DISPLAY (display)) { diff --git a/src/libandroid/wayland_server.c b/src/libandroid/wayland_server.c new file mode 100644 index 00000000..e3c3d8be --- /dev/null +++ b/src/libandroid/wayland_server.c @@ -0,0 +1,257 @@ +#include +#include +#include +#include +#include +#include +#include + +#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; +} diff --git a/src/libandroid/wayland_server.h b/src/libandroid/wayland_server.h new file mode 100644 index 00000000..58d3ec42 --- /dev/null +++ b/src/libandroid/wayland_server.h @@ -0,0 +1,3 @@ +struct wl_display; + +struct wl_display *wayland_server_start(void);