/* * Copyright (C) 2016 Igalia S.L. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "config.h" #include "WaylandCompositor.h" #if PLATFORM(WAYLAND) && USE(EGL) #include "WebKit2WaylandServerProtocol.h" #include #include #include #include #include #include #include #if USE(OPENGL_ES_2) #include #include #else #include #endif using namespace WebCore; namespace WebKit { #if !defined(PFNEGLBINDWAYLANDDISPLAYWL) typedef EGLBoolean (*PFNEGLBINDWAYLANDDISPLAYWL) (EGLDisplay, struct wl_display*); #endif #if !defined(PFNEGLUNBINDWAYLANDDISPLAYWL) typedef EGLBoolean (*PFNEGLUNBINDWAYLANDDISPLAYWL) (EGLDisplay, struct wl_display*); #endif #if !defined(PFNEGLQUERYWAYLANDBUFFERWL) typedef EGLBoolean (*PFNEGLQUERYWAYLANDBUFFERWL) (EGLDisplay, struct wl_resource*, EGLint attribute, EGLint* value); #endif #if !defined(PFNEGLCREATEIMAGEKHRPROC) typedef EGLImageKHR (*PFNEGLCREATEIMAGEKHRPROC) (EGLDisplay, EGLContext, EGLenum target, EGLClientBuffer, const EGLint* attribList); #endif #if !defined(PFNEGLDESTROYIMAGEKHRPROC) typedef EGLBoolean (*PFNEGLDESTROYIMAGEKHRPROC) (EGLDisplay, EGLImageKHR); #endif #if !defined(PFNGLEGLIMAGETARGETTEXTURE2DOESPROC) typedef void (*PFNGLEGLIMAGETARGETTEXTURE2DOESPROC) (GLenum target, GLeglImageOES); #endif static PFNEGLBINDWAYLANDDISPLAYWL eglBindWaylandDisplay; static PFNEGLUNBINDWAYLANDDISPLAYWL eglUnbindWaylandDisplay; static PFNEGLQUERYWAYLANDBUFFERWL eglQueryWaylandBuffer; static PFNEGLCREATEIMAGEKHRPROC eglCreateImage; static PFNEGLDESTROYIMAGEKHRPROC eglDestroyImage; static PFNGLEGLIMAGETARGETTEXTURE2DOESPROC glImageTargetTexture2D; WaylandCompositor& WaylandCompositor::singleton() { static NeverDestroyed waylandCompositor; return waylandCompositor; } WaylandCompositor::Buffer* WaylandCompositor::Buffer::getOrCreate(struct wl_resource* resource) { if (struct wl_listener* listener = wl_resource_get_destroy_listener(resource, destroyListenerCallback)) { WaylandCompositor::Buffer* buffer; return wl_container_of(listener, buffer, m_destroyListener); } return new WaylandCompositor::Buffer(resource); } WaylandCompositor::Buffer::Buffer(struct wl_resource* resource) : m_resource(resource) , m_weakPtrFactory(this) { wl_list_init(&m_destroyListener.link); m_destroyListener.notify = destroyListenerCallback; wl_resource_add_destroy_listener(m_resource, &m_destroyListener); } WaylandCompositor::Buffer::~Buffer() { wl_list_remove(&m_destroyListener.link); } void WaylandCompositor::Buffer::destroyListenerCallback(struct wl_listener* listener, void*) { WaylandCompositor::Buffer* buffer; buffer = wl_container_of(listener, buffer, m_destroyListener); delete buffer; } void WaylandCompositor::Buffer::use() { m_busyCount++; } void WaylandCompositor::Buffer::unuse() { m_busyCount--; if (!m_busyCount) wl_resource_queue_event(m_resource, WL_BUFFER_RELEASE); } EGLImageKHR WaylandCompositor::Buffer::createImage() const { return static_cast(eglCreateImage(PlatformDisplay::sharedDisplay().eglDisplay(), EGL_NO_CONTEXT, EGL_WAYLAND_BUFFER_WL, m_resource, nullptr)); } IntSize WaylandCompositor::Buffer::size() const { EGLDisplay eglDisplay = PlatformDisplay::sharedDisplay().eglDisplay(); int width, height; eglQueryWaylandBuffer(eglDisplay, m_resource, EGL_WIDTH, &width); eglQueryWaylandBuffer(eglDisplay, m_resource, EGL_HEIGHT, &height); return { width, height }; } WaylandCompositor::Surface::Surface() : m_image(EGL_NO_IMAGE_KHR) { glGenTextures(1, &m_texture); glBindTexture(GL_TEXTURE_2D, m_texture); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); } WaylandCompositor::Surface::~Surface() { // Destroy pending frame callbacks. auto list = WTFMove(m_frameCallbackList); for (auto* resource : list) wl_resource_destroy(resource); if (m_buffer) m_buffer->unuse(); if (m_image != EGL_NO_IMAGE_KHR) eglDestroyImage(PlatformDisplay::sharedDisplay().eglDisplay(), m_image); glDeleteTextures(1, &m_texture); } void WaylandCompositor::Surface::makePendingBufferCurrent() { if (m_pendingBuffer == m_buffer) return; if (m_buffer) m_buffer->unuse(); if (m_pendingBuffer) m_pendingBuffer->use(); m_buffer = m_pendingBuffer; } void WaylandCompositor::Surface::attachBuffer(struct wl_resource* buffer) { if (m_pendingBuffer) m_pendingBuffer = nullptr; if (buffer) { auto* compositorBuffer = WaylandCompositor::Buffer::getOrCreate(buffer); m_pendingBuffer = compositorBuffer->createWeakPtr(); } } void WaylandCompositor::Surface::requestFrame(struct wl_resource* resource) { wl_resource_set_implementation(resource, nullptr, this, [](struct wl_resource* resource) { auto* surface = static_cast(wl_resource_get_user_data(resource)); if (size_t item = surface->m_frameCallbackList.find(resource) != notFound) surface->m_frameCallbackList.remove(item); }); m_frameCallbackList.append(resource); } bool WaylandCompositor::Surface::prepareTextureForPainting(unsigned& texture, IntSize& textureSize) { if (m_image == EGL_NO_IMAGE_KHR) return false; glBindTexture(GL_TEXTURE_2D, m_texture); glImageTargetTexture2D(GL_TEXTURE_2D, m_image); texture = m_texture; textureSize = m_imageSize; return true; } void WaylandCompositor::Surface::commit() { EGLDisplay eglDisplay = PlatformDisplay::sharedDisplay().eglDisplay(); if (m_image != EGL_NO_IMAGE_KHR) eglDestroyImage(eglDisplay, m_image); m_image = m_pendingBuffer->createImage(); if (m_image == EGL_NO_IMAGE_KHR) return; m_imageSize = m_pendingBuffer->size(); makePendingBufferCurrent(); if (m_webPage) m_webPage->setViewNeedsDisplay(IntRect(IntPoint::zero(), m_webPage->viewSize())); // From a Wayland point-of-view frame callbacks should be fired where the // compositor knows it has *used* the committed contents, so firing them here // can be surprising but we don't need them as a throttling mechanism because // rendering synchronization is handled elsewhere by WebKit. auto list = WTFMove(m_frameCallbackList); for (auto* resource : list) { wl_callback_send_done(resource, 0); wl_resource_destroy(resource); } } static const struct wl_surface_interface surfaceInterface = { // destroyCallback [](struct wl_client*, struct wl_resource* resource) { wl_resource_destroy(resource); }, // attachCallback [](struct wl_client* client, struct wl_resource* resource, struct wl_resource* buffer, int32_t sx, int32_t sy) { auto* surface = static_cast(wl_resource_get_user_data(resource)); if (!surface) return; EGLint format; if (!eglQueryWaylandBuffer(PlatformDisplay::sharedDisplay().eglDisplay(), buffer, EGL_TEXTURE_FORMAT, &format) || (format != EGL_TEXTURE_RGB && format != EGL_TEXTURE_RGBA)) return; surface->attachBuffer(buffer); }, // damageCallback [](struct wl_client*, struct wl_resource*, int32_t, int32_t, int32_t, int32_t) { }, // frameCallback [](struct wl_client* client, struct wl_resource* resource, uint32_t id) { auto* surface = static_cast(wl_resource_get_user_data(resource)); if (!surface) return; if (struct wl_resource* callbackResource = wl_resource_create(client, &wl_callback_interface, 1, id)) surface->requestFrame(callbackResource); else wl_client_post_no_memory(client); }, // setOpaqueRegionCallback [](struct wl_client*, struct wl_resource*, struct wl_resource*) { }, // setInputRegionCallback [](struct wl_client*, struct wl_resource*, struct wl_resource*) { }, // commitCallback [](struct wl_client* client, struct wl_resource* resource) { auto* surface = static_cast(wl_resource_get_user_data(resource)); if (!surface) return; surface->commit(); }, // setBufferTransformCallback [](struct wl_client*, struct wl_resource*, int32_t) { }, // setBufferScaleCallback [](struct wl_client*, struct wl_resource*, int32_t) { }, #if WAYLAND_VERSION_MAJOR > 1 || (WAYLAND_VERSION_MAJOR == 1 && WAYLAND_VERSION_MINOR >= 10) // damageBufferCallback [](struct wl_client*, struct wl_resource*, int32_t, int32_t, int32_t, int32_t) { }, #endif }; static const struct wl_compositor_interface compositorInterface = { // createSurfaceCallback [](struct wl_client* client, struct wl_resource* resource, uint32_t id) { if (struct wl_resource* surfaceResource = wl_resource_create(client, &wl_surface_interface, 1, id)) { wl_resource_set_implementation(surfaceResource, &surfaceInterface, new WaylandCompositor::Surface(), [](struct wl_resource* resource) { auto* surface = static_cast(wl_resource_get_user_data(resource)); delete surface; }); } else wl_client_post_no_memory(client); }, // createRegionCallback [](struct wl_client*, struct wl_resource*, uint32_t) { } }; static const struct wl_webkitgtk_interface webkitgtkInterface = { // bindSurfaceToPageCallback [](struct wl_client*, struct wl_resource* resource, struct wl_resource* surfaceResource, uint32_t pageID) { auto* surface = static_cast(wl_resource_get_user_data(surfaceResource)); if (!surface) return; auto* compositor = static_cast(wl_resource_get_user_data(resource)); compositor->bindSurfaceToWebPage(surface, pageID); } }; bool WaylandCompositor::initializeEGL() { if (PlatformDisplay::sharedDisplay().eglCheckVersion(1, 5)) { eglCreateImage = reinterpret_cast(eglGetProcAddress("eglCreateImage")); eglDestroyImage = reinterpret_cast(eglGetProcAddress("eglDestroyImage")); } else { const char* extensions = eglQueryString(PlatformDisplay::sharedDisplay().eglDisplay(), EGL_EXTENSIONS); if (GLContext::isExtensionSupported(extensions, "EGL_KHR_image_base")) { eglCreateImage = reinterpret_cast(eglGetProcAddress("eglCreateImageKHR")); eglDestroyImage = reinterpret_cast(eglGetProcAddress("eglDestroyImageKHR")); } } if (!eglCreateImage || !eglDestroyImage) { WTFLogAlways("WaylandCompositor requires eglCreateImage and eglDestroyImage."); return false; } glImageTargetTexture2D = reinterpret_cast(eglGetProcAddress("glEGLImageTargetTexture2DOES")); if (!glImageTargetTexture2D) { WTFLogAlways("WaylandCompositor requires glEGLImageTargetTexture2D."); return false; } eglQueryWaylandBuffer = reinterpret_cast(eglGetProcAddress("eglQueryWaylandBufferWL")); if (!eglQueryWaylandBuffer) { WTFLogAlways("WaylandCompositor requires eglQueryWaylandBuffer."); return false; } eglBindWaylandDisplay = reinterpret_cast(eglGetProcAddress("eglBindWaylandDisplayWL")); eglUnbindWaylandDisplay = reinterpret_cast(eglGetProcAddress("eglUnbindWaylandDisplayWL")); if (!eglBindWaylandDisplay || !eglUnbindWaylandDisplay) { WTFLogAlways("WaylandCompositor requires eglBindWaylandDisplayWL and eglUnbindWaylandDisplayWL."); return false; } m_eglContext = GLContext::createOffscreenContext(); if (!m_eglContext) return false; if (!m_eglContext->makeContextCurrent()) return false; return true; } typedef struct { GSource source; gpointer fdTag; struct wl_display* display; } WaylandLoopSource; static const unsigned waylandLoopSourceCondition = G_IO_IN | G_IO_HUP | G_IO_ERR; static GSourceFuncs waylandLoopSourceFunctions = { // prepare [](GSource *source, int *timeout) -> gboolean { *timeout = -1; auto* wlLoopSource = reinterpret_cast(source); wl_display_flush_clients(wlLoopSource->display); return FALSE; }, nullptr, // check // dispatch [](GSource* source, GSourceFunc callback, gpointer userData) -> gboolean { auto* wlLoopSource = reinterpret_cast(source); unsigned events = g_source_query_unix_fd(source, wlLoopSource->fdTag) & waylandLoopSourceCondition; if (events & G_IO_HUP || events & G_IO_ERR) { WTFLogAlways("Wayland Display Event Source: lost connection to nested Wayland compositor"); return G_SOURCE_REMOVE; } if (events & G_IO_IN) wl_event_loop_dispatch(wl_display_get_event_loop(wlLoopSource->display), 0); return G_SOURCE_CONTINUE; }, nullptr, // finalize nullptr, // closure_callback nullptr, // closure_marshall }; static GRefPtr createWaylandLoopSource(struct wl_display* display) { GRefPtr source = adoptGRef(g_source_new(&waylandLoopSourceFunctions, sizeof(WaylandLoopSource))); g_source_set_name(source.get(), "Nested Wayland compositor display event source"); g_source_set_priority(source.get(), G_PRIORITY_DEFAULT + 1); auto* wlLoopSource = reinterpret_cast(source.get()); wlLoopSource->display = display; wlLoopSource->fdTag = g_source_add_unix_fd(source.get(), wl_event_loop_get_fd(wl_display_get_event_loop(display)), static_cast(waylandLoopSourceCondition)); g_source_attach(source.get(), nullptr); return source; } WaylandCompositor::WaylandCompositor() { WlUniquePtr display(wl_display_create()); if (!display) { WTFLogAlways("Nested Wayland compositor could not create display object"); return; } String displayName = "webkitgtk-wayland-compositor-" + createCanonicalUUIDString(); if (wl_display_add_socket(display.get(), displayName.utf8().data()) == -1) { WTFLogAlways("Nested Wayland compositor could not create display socket"); return; } WlUniquePtr compositorGlobal(wl_global_create(display.get(), &wl_compositor_interface, wl_compositor_interface.version, this, [](struct wl_client* client, void* data, uint32_t version, uint32_t id) { if (struct wl_resource* resource = wl_resource_create(client, &wl_compositor_interface, std::min(static_cast(version), 3), id)) wl_resource_set_implementation(resource, &compositorInterface, static_cast(data), nullptr); else wl_client_post_no_memory(client); })); if (!compositorGlobal) { WTFLogAlways("Nested Wayland compositor could not register compositor global"); return; } WlUniquePtr webkitgtkGlobal(wl_global_create(display.get(), &wl_webkitgtk_interface, 1, this, [](struct wl_client* client, void* data, uint32_t version, uint32_t id) { if (struct wl_resource* resource = wl_resource_create(client, &wl_webkitgtk_interface, 1, id)) wl_resource_set_implementation(resource, &webkitgtkInterface, static_cast(data), nullptr); else wl_client_post_no_memory(client); })); if (!webkitgtkGlobal) { WTFLogAlways("Nested Wayland compositor could not register webkitgtk global"); return; } if (!initializeEGL()) { WTFLogAlways("Nested Wayland compositor could not initialize EGL"); return; } if (!eglBindWaylandDisplay(PlatformDisplay::sharedDisplay().eglDisplay(), display.get())) { WTFLogAlways("Nested Wayland compositor could not bind nested display"); return; } m_displayName = WTFMove(displayName); m_display = WTFMove(display); m_compositorGlobal = WTFMove(compositorGlobal); m_webkitgtkGlobal = WTFMove(webkitgtkGlobal); m_eventSource = createWaylandLoopSource(m_display.get()); } bool WaylandCompositor::getTexture(WebPageProxy& webPage, unsigned& texture, IntSize& textureSize) { if (auto* surface = m_pageMap.get(&webPage)) return surface->prepareTextureForPainting(texture, textureSize); return false; } void WaylandCompositor::bindSurfaceToWebPage(WaylandCompositor::Surface* surface, uint64_t pageID) { WebPageProxy* webPage = nullptr; for (auto* page : m_pageMap.keys()) { if (page->pageID() == pageID) { webPage = page; break; } } if (!webPage) return; surface->setWebPage(webPage); m_pageMap.set(webPage, surface); } void WaylandCompositor::registerWebPage(WebPageProxy& webPage) { m_pageMap.add(&webPage, nullptr); } void WaylandCompositor::unregisterWebPage(WebPageProxy& webPage) { if (auto* surface = m_pageMap.take(&webPage)) surface->setWebPage(nullptr); } } // namespace WebKit #endif // PLATFORM(WAYLAND) && USE(EGL)