1 /* Copyright (C) <2018> Philippe Normand <philn@igalia.com>
2 * Copyright (C) <2018> Žan Doberšek <zdobersek@igalia.com>
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Library General Public
6 * License as published by the Free Software Foundation; either
7 * version 2 of the License, or (at your option) any later version.
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Library General Public License for more details.
14 * You should have received a copy of the GNU Library General Public
15 * License along with this library; if not, write to the
16 * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
17 * Boston, MA 02110-1301, USA.
24 #include "WPEThreadedView.h"
26 #include <gst/gl/gl.h>
27 #include <gst/gl/egl/gsteglimage.h>
28 #include <gst/gl/egl/gstgldisplay_egl.h>
33 GST_DEBUG_CATEGORY_EXTERN (wpe_src_debug);
34 #define GST_CAT_DEFAULT wpe_src_debug
36 #if defined(WPE_FDO_CHECK_VERSION) && WPE_FDO_CHECK_VERSION(1, 3, 0)
37 #define USE_DEPRECATED_FDO_EGL_IMAGE 0
38 #define WPE_GLIB_SOURCE_PRIORITY G_PRIORITY_DEFAULT
40 #define USE_DEPRECATED_FDO_EGL_IMAGE 1
41 #define WPE_GLIB_SOURCE_PRIORITY -70
46 GMutexHolder(GMutex& mutex)
60 WPEThreadedView::WPEThreadedView()
62 g_mutex_init(&threading.mutex);
63 g_cond_init(&threading.cond);
64 g_mutex_init(&threading.ready_mutex);
65 g_cond_init(&threading.ready_cond);
67 g_mutex_init(&images.mutex);
70 GMutexHolder lock(threading.mutex);
71 threading.thread = g_thread_new("WPEThreadedView",
73 g_cond_wait(&threading.cond, &threading.mutex);
74 GST_DEBUG("thread spawned");
78 WPEThreadedView::~WPEThreadedView()
81 GMutexHolder lock(images.mutex);
84 gst_egl_image_unref(images.pending);
85 images.pending = nullptr;
87 if (images.committed) {
88 gst_egl_image_unref(images.committed);
89 images.committed = nullptr;
94 GMutexHolder lock(threading.mutex);
95 wpe_view_backend_exportable_fdo_destroy(wpe.exportable);
99 gst_object_unref(gst.display);
100 gst.display = nullptr;
104 gst_object_unref(gst.context);
105 gst.context = nullptr;
108 if (threading.thread) {
109 g_thread_unref(threading.thread);
110 threading.thread = nullptr;
113 g_mutex_clear(&threading.mutex);
114 g_cond_clear(&threading.cond);
115 g_mutex_clear(&threading.ready_mutex);
116 g_cond_clear(&threading.ready_cond);
117 g_mutex_clear(&images.mutex);
120 gpointer WPEThreadedView::s_viewThread(gpointer data)
122 auto& view = *static_cast<WPEThreadedView*>(data);
124 view.glib.context = g_main_context_new();
125 view.glib.loop = g_main_loop_new(view.glib.context, FALSE);
127 g_main_context_push_thread_default(view.glib.context);
130 GSource* source = g_idle_source_new();
131 g_source_set_callback(source,
132 [](gpointer data) -> gboolean {
133 auto& view = *static_cast<WPEThreadedView*>(data);
134 GMutexHolder lock(view.threading.mutex);
135 g_cond_signal(&view.threading.cond);
136 return G_SOURCE_REMOVE;
139 g_source_attach(source, view.glib.context);
140 g_source_unref(source);
143 g_main_loop_run(view.glib.loop);
145 g_main_loop_unref(view.glib.loop);
146 view.glib.loop = nullptr;
148 if (view.webkit.view) {
149 g_object_unref(view.webkit.view);
150 view.webkit.view = nullptr;
152 if (view.webkit.uri) {
153 g_free(view.webkit.uri);
154 view.webkit.uri = nullptr;
157 g_main_context_pop_thread_default(view.glib.context);
158 g_main_context_unref(view.glib.context);
159 view.glib.context = nullptr;
163 struct wpe_view_backend* WPEThreadedView::backend() const
165 return wpe.exportable ? wpe_view_backend_exportable_fdo_get_view_backend(wpe.exportable) : nullptr;
168 void WPEThreadedView::s_loadEvent(WebKitWebView*, WebKitLoadEvent event, gpointer data)
170 if (event == WEBKIT_LOAD_COMMITTED) {
171 auto& view = *static_cast<WPEThreadedView*>(data);
172 GMutexHolder lock(view.threading.ready_mutex);
173 g_cond_signal(&view.threading.ready_cond);
177 bool WPEThreadedView::initialize(GstWpeSrc* src, GstGLContext* context, GstGLDisplay* display, int width, int height)
179 GST_DEBUG("context %p display %p, size (%d,%d)", context, display, width, height);
181 static std::once_flag s_loaderFlag;
182 std::call_once(s_loaderFlag,
184 #if defined(WPE_BACKEND_CHECK_VERSION) && WPE_BACKEND_CHECK_VERSION(1, 2, 0)
185 wpe_loader_init("libWPEBackend-fdo-1.0.so");
189 EGLDisplay eglDisplay = gst_gl_display_egl_get_from_native(GST_GL_DISPLAY_TYPE_WAYLAND,
190 gst_gl_display_get_handle(display));
191 GST_DEBUG("eglDisplay %p", eglDisplay);
193 struct InitializeContext {
195 WPEThreadedView& view;
196 GstGLContext* context;
197 GstGLDisplay* display;
198 EGLDisplay eglDisplay;
202 } initializeContext { src, *this, context, display, eglDisplay, width, height, FALSE };
204 GSource* source = g_idle_source_new();
205 g_source_set_callback(source,
206 [](gpointer data) -> gboolean {
207 GST_DEBUG("on view thread");
208 auto& initializeContext = *static_cast<InitializeContext*>(data);
209 auto& view = initializeContext.view;
211 GMutexHolder lock(view.threading.mutex);
213 view.gst.context = GST_GL_CONTEXT(gst_object_ref(initializeContext.context));
214 view.gst.display = GST_GL_DISPLAY(gst_object_ref(initializeContext.display));
216 view.wpe.width = initializeContext.width;
217 view.wpe.height = initializeContext.height;
219 initializeContext.result = wpe_fdo_initialize_for_egl_display(initializeContext.eglDisplay);
220 GST_DEBUG("FDO EGL display initialisation result: %d", initializeContext.result);
221 if (!initializeContext.result) {
222 g_cond_signal(&view.threading.cond);
223 return G_SOURCE_REMOVE;
226 view.wpe.exportable = wpe_view_backend_exportable_fdo_egl_create(&s_exportableClient,
227 &view, view.wpe.width, view.wpe.height);
228 auto* wpeViewBackend = wpe_view_backend_exportable_fdo_get_view_backend(view.wpe.exportable);
229 auto* viewBackend = webkit_web_view_backend_new(wpeViewBackend, nullptr, nullptr);
230 #if defined(WPE_BACKEND_CHECK_VERSION) && WPE_BACKEND_CHECK_VERSION(1, 1, 0)
231 wpe_view_backend_add_activity_state(wpeViewBackend, wpe_view_activity_state_visible | wpe_view_activity_state_focused | wpe_view_activity_state_in_window);
234 view.webkit.view = WEBKIT_WEB_VIEW(g_object_new(WEBKIT_TYPE_WEB_VIEW,
235 "backend", viewBackend, nullptr));
237 gst_wpe_src_configure_web_view(initializeContext.src, view.webkit.view);
239 g_signal_connect(view.webkit.view, "load-changed", G_CALLBACK(s_loadEvent), &view);
241 const gchar* location;
242 gboolean drawBackground = TRUE;
243 g_object_get(initializeContext.src, "location", &location, "draw-background", &drawBackground, nullptr);
245 g_warning("Invalid location");
247 view.setDrawBackground(drawBackground);
248 view.loadUriUnlocked(location);
250 g_cond_signal(&view.threading.cond);
251 return G_SOURCE_REMOVE;
253 &initializeContext, nullptr);
254 g_source_set_priority(source, WPE_GLIB_SOURCE_PRIORITY);
257 GMutexHolder lock(threading.mutex);
258 g_source_attach(source, glib.context);
259 g_cond_wait(&threading.cond, &threading.mutex);
262 g_source_unref(source);
264 if (initializeContext.result) {
265 GST_DEBUG("waiting load to finish");
266 GMutexHolder lock(threading.ready_mutex);
267 g_cond_wait(&threading.ready_cond, &threading.ready_mutex);
270 return initializeContext.result;
273 GstEGLImage* WPEThreadedView::image()
275 GstEGLImage* ret = nullptr;
276 GMutexHolder lock(images.mutex);
278 GST_TRACE("pending %" GST_PTR_FORMAT " (%d) committed %" GST_PTR_FORMAT " (%d)", images.pending,
279 GST_IS_EGL_IMAGE(images.pending) ? GST_MINI_OBJECT_REFCOUNT_VALUE(GST_MINI_OBJECT_CAST(images.pending)) : 0,
281 GST_IS_EGL_IMAGE(images.committed) ? GST_MINI_OBJECT_REFCOUNT_VALUE(GST_MINI_OBJECT_CAST(images.committed)) : 0);
283 if (images.pending) {
284 auto* previousImage = images.committed;
285 images.committed = images.pending;
286 images.pending = nullptr;
289 gst_egl_image_unref(previousImage);
292 if (images.committed) {
293 ret = images.committed;
300 void WPEThreadedView::resize(int width, int height)
302 GST_DEBUG("resize to %dx%d", width, height);
306 GSource* source = g_idle_source_new();
307 g_source_set_callback(source,
308 [](gpointer data) -> gboolean {
309 auto& view = *static_cast<WPEThreadedView*>(data);
310 GMutexHolder lock(view.threading.mutex);
312 GST_DEBUG("dispatching");
313 if (view.wpe.exportable && wpe_view_backend_exportable_fdo_get_view_backend(view.wpe.exportable))
314 wpe_view_backend_dispatch_set_size(wpe_view_backend_exportable_fdo_get_view_backend(view.wpe.exportable), view.wpe.width, view.wpe.height);
316 g_cond_signal(&view.threading.cond);
317 return G_SOURCE_REMOVE;
320 g_source_set_priority(source, WPE_GLIB_SOURCE_PRIORITY);
323 GMutexHolder lock(threading.mutex);
324 g_source_attach(source, glib.context);
325 g_cond_wait(&threading.cond, &threading.mutex);
328 g_source_unref(source);
331 void WPEThreadedView::frameComplete()
333 GST_TRACE("frame complete");
335 GSource* source = g_idle_source_new();
336 g_source_set_callback(source,
337 [](gpointer data) -> gboolean {
338 auto& view = *static_cast<WPEThreadedView*>(data);
339 GMutexHolder lock(view.threading.mutex);
341 GST_TRACE("dispatching");
342 wpe_view_backend_exportable_fdo_dispatch_frame_complete(view.wpe.exportable);
344 g_cond_signal(&view.threading.cond);
345 return G_SOURCE_REMOVE;
348 g_source_set_priority(source, WPE_GLIB_SOURCE_PRIORITY);
351 GMutexHolder lock(threading.mutex);
352 g_source_attach(source, glib.context);
353 g_cond_wait(&threading.cond, &threading.mutex);
356 g_source_unref(source);
359 void WPEThreadedView::loadUriUnlocked(const gchar* uri)
364 GST_DEBUG("loading %s", uri);
365 webkit.uri = g_strdup(uri);
366 webkit_web_view_load_uri(webkit.view, webkit.uri);
369 void WPEThreadedView::loadUri(const gchar* uri)
372 WPEThreadedView& view;
374 } uriContext { *this, uri };
376 GSource* source = g_idle_source_new();
377 g_source_set_callback(source,
378 [](gpointer data) -> gboolean {
379 GST_DEBUG("on view thread");
380 auto& uriContext = *static_cast<UriContext*>(data);
381 auto& view = uriContext.view;
382 GMutexHolder lock(view.threading.mutex);
384 view.loadUriUnlocked(uriContext.uri);
386 g_cond_signal(&view.threading.cond);
387 return G_SOURCE_REMOVE;
389 &uriContext, nullptr);
390 g_source_set_priority(source, WPE_GLIB_SOURCE_PRIORITY);
393 GMutexHolder lock(threading.mutex);
394 g_source_attach(source, glib.context);
395 g_cond_wait(&threading.cond, &threading.mutex);
399 g_source_unref(source);
402 void WPEThreadedView::setDrawBackground(gboolean drawsBackground)
404 #if WEBKIT_CHECK_VERSION(2, 24, 0)
405 GST_DEBUG("%s background rendering", drawsBackground ? "Enabling" : "Disabling");
407 webkit_color_parse(&color, drawsBackground ? "white" : "transparent");
408 webkit_web_view_set_background_color(webkit.view, &color);
410 GST_FIXME("webkit_web_view_set_background_color is not implemented in WPE %u.%u. Please upgrade to 2.24", webkit_get_major_version(), webkit_get_minor_version());
414 void WPEThreadedView::releaseImage(gpointer imagePointer)
416 struct ReleaseImageContext {
417 WPEThreadedView& view;
418 gpointer imagePointer;
419 } releaseImageContext{ *this, imagePointer };
421 GSource* source = g_idle_source_new();
422 g_source_set_callback(source,
423 [](gpointer data) -> gboolean {
424 auto& releaseImageContext = *static_cast<ReleaseImageContext*>(data);
425 auto& view = releaseImageContext.view;
426 GMutexHolder lock(view.threading.mutex);
428 GST_TRACE("Dispatch release exported image %p", releaseImageContext.imagePointer);
429 #if USE_DEPRECATED_FDO_EGL_IMAGE
430 wpe_view_backend_exportable_fdo_egl_dispatch_release_image(releaseImageContext.view.wpe.exportable,
431 static_cast<EGLImageKHR>(releaseImageContext.imagePointer));
433 wpe_view_backend_exportable_fdo_egl_dispatch_release_exported_image(releaseImageContext.view.wpe.exportable,
434 static_cast<struct wpe_fdo_egl_exported_image*>(releaseImageContext.imagePointer));
436 g_cond_signal(&view.threading.cond);
437 return G_SOURCE_REMOVE;
439 &releaseImageContext, nullptr);
440 g_source_set_priority(source, WPE_GLIB_SOURCE_PRIORITY);
443 GMutexHolder lock(threading.mutex);
444 g_source_attach(source, glib.context);
445 g_cond_wait(&threading.cond, &threading.mutex);
448 g_source_unref(source);
451 struct ImageContext {
452 WPEThreadedView* view;
456 void WPEThreadedView::handleExportedImage(gpointer image)
458 ImageContext* imageContext = g_slice_new(ImageContext);
459 imageContext->view = this;
460 imageContext->image = static_cast<gpointer>(image);
461 EGLImageKHR eglImage;
462 #if USE_DEPRECATED_FDO_EGL_IMAGE
463 eglImage = static_cast<EGLImageKHR>(image);
465 eglImage = wpe_fdo_egl_exported_image_get_egl_image(static_cast<struct wpe_fdo_egl_exported_image*>(image));
468 auto* gstImage = gst_egl_image_new_wrapped(gst.context, eglImage, GST_GL_RGBA, imageContext, s_releaseImage);
470 GMutexHolder lock(images.mutex);
472 GST_TRACE("EGLImage %p wrapped in GstEGLImage %" GST_PTR_FORMAT, eglImage, gstImage);
473 images.pending = gstImage;
477 struct wpe_view_backend_exportable_fdo_egl_client WPEThreadedView::s_exportableClient = {
478 #if USE_DEPRECATED_FDO_EGL_IMAGE
480 [](void* data, EGLImageKHR image) {
481 auto& view = *static_cast<WPEThreadedView*>(data);
482 view.handleExportedImage(static_cast<gpointer>(image));
488 [](void* data, struct wpe_fdo_egl_exported_image* image) {
489 auto& view = *static_cast<WPEThreadedView*>(data);
490 view.handleExportedImage(static_cast<gpointer>(image));
494 nullptr, nullptr, nullptr
497 void WPEThreadedView::s_releaseImage(GstEGLImage* image, gpointer data)
499 ImageContext* context = static_cast<ImageContext*>(data);
500 context->view->releaseImage(context->image);
501 g_slice_free(ImageContext, context);