wpesrc: Implement webview background configuration support
[platform/upstream/gstreamer.git] / ext / wpe / WPEThreadedView.cpp
1 /* Copyright (C) <2018> Philippe Normand <philn@igalia.com>
2  * Copyright (C) <2018> Žan Doberšek <zdobersek@igalia.com>
3  *
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.
8  *
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.
13  *
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.
18  */
19
20 #include "WPEThreadedView.h"
21
22 #include <cstdio>
23 #include <mutex>
24
25 #define GST_CAT_DEFAULT wpe_src_debug
26
27 // -70 is the GLib priority we use internally in WebKit, for WPE.
28 #define WPE_GLIB_SOURCE_PRIORITY -70
29
30 class GMutexHolder {
31 public:
32     GMutexHolder(GMutex& mutex)
33         : m(mutex)
34     {
35         g_mutex_lock(&m);
36     }
37     ~GMutexHolder()
38     {
39         g_mutex_unlock(&m);
40     }
41
42 private:
43     GMutex& m;
44 };
45
46 WPEThreadedView::WPEThreadedView()
47 {
48     g_mutex_init(&threading.mutex);
49     g_cond_init(&threading.cond);
50     g_mutex_init(&threading.ready_mutex);
51     g_cond_init(&threading.ready_cond);
52
53     g_mutex_init(&images.mutex);
54
55     {
56         GMutexHolder lock(threading.mutex);
57         threading.thread = g_thread_new("WPEThreadedView",
58             s_viewThread, this);
59         g_cond_wait(&threading.cond, &threading.mutex);
60         GST_DEBUG("thread spawned");
61     }
62 }
63
64 WPEThreadedView::~WPEThreadedView()
65 {
66     {
67         GMutexHolder lock(images.mutex);
68
69         if (images.pending) {
70             gst_egl_image_unref(images.pending);
71             images.pending = nullptr;
72         }
73         if (images.committed) {
74             gst_egl_image_unref(images.committed);
75             images.committed = nullptr;
76         }
77     }
78
79     {
80         GMutexHolder lock(threading.mutex);
81         wpe_view_backend_exportable_fdo_destroy(wpe.exportable);
82     }
83
84     if (gst.display) {
85         gst_object_unref(gst.display);
86         gst.display = nullptr;
87     }
88
89     if (gst.context) {
90         gst_object_unref(gst.context);
91         gst.context = nullptr;
92     }
93
94     if (threading.thread) {
95         g_thread_unref(threading.thread);
96         threading.thread = nullptr;
97     }
98
99     g_mutex_clear(&threading.mutex);
100     g_cond_clear(&threading.cond);
101     g_mutex_clear(&threading.ready_mutex);
102     g_cond_clear(&threading.ready_cond);
103     g_mutex_clear(&images.mutex);
104 }
105
106 gpointer WPEThreadedView::s_viewThread(gpointer data)
107 {
108     auto& view = *static_cast<WPEThreadedView*>(data);
109
110     view.glib.context = g_main_context_new();
111     view.glib.loop = g_main_loop_new(view.glib.context, FALSE);
112
113     g_main_context_push_thread_default(view.glib.context);
114
115     {
116         GSource* source = g_idle_source_new();
117         g_source_set_callback(source,
118             [](gpointer data) -> gboolean {
119                 auto& view = *static_cast<WPEThreadedView*>(data);
120                 GMutexHolder lock(view.threading.mutex);
121                 g_cond_signal(&view.threading.cond);
122                 return G_SOURCE_REMOVE;
123             },
124             &view, nullptr);
125         g_source_attach(source, view.glib.context);
126         g_source_unref(source);
127     }
128
129     g_main_loop_run(view.glib.loop);
130
131     g_main_loop_unref(view.glib.loop);
132     view.glib.loop = nullptr;
133
134     if (view.webkit.view) {
135         g_object_unref(view.webkit.view);
136         view.webkit.view = nullptr;
137     }
138     if (view.webkit.uri) {
139         g_free(view.webkit.uri);
140         view.webkit.uri = nullptr;
141     }
142
143     g_main_context_pop_thread_default(view.glib.context);
144     g_main_context_unref(view.glib.context);
145     view.glib.context = nullptr;
146     return nullptr;
147 }
148
149 struct wpe_view_backend* WPEThreadedView::backend() const
150 {
151     return wpe.exportable ? wpe_view_backend_exportable_fdo_get_view_backend(wpe.exportable) : nullptr;
152 }
153
154 void WPEThreadedView::s_loadEvent(WebKitWebView*, WebKitLoadEvent event, gpointer data)
155 {
156     if (event == WEBKIT_LOAD_COMMITTED) {
157         auto& view = *static_cast<WPEThreadedView*>(data);
158         GMutexHolder lock(view.threading.ready_mutex);
159         g_cond_signal(&view.threading.ready_cond);
160     }
161 }
162
163 void WPEThreadedView::initialize(GstWpeSrc* src, GstGLContext* context, GstGLDisplay* display, int width, int height)
164 {
165     GST_DEBUG("context %p display %p, size (%d,%d)", context, display, width, height);
166
167     static std::once_flag s_loaderFlag;
168     std::call_once(s_loaderFlag,
169         [] {
170 #if defined(WPE_BACKEND_CHECK_VERSION) && WPE_BACKEND_CHECK_VERSION(0, 2, 0)
171             wpe_loader_init("libWPEBackend-fdo-0.1.so");
172 #endif
173         });
174
175     struct InitializeContext {
176         GstWpeSrc* src;
177         WPEThreadedView& view;
178         GstGLContext* context;
179         GstGLDisplay* display;
180         int width;
181         int height;
182     } initializeContext{ src, *this, context, display, width, height };
183
184     GSource* source = g_idle_source_new();
185     g_source_set_callback(source,
186         [](gpointer data) -> gboolean {
187             GST_DEBUG("on view thread");
188             auto& initializeContext = *static_cast<InitializeContext*>(data);
189             auto& view = initializeContext.view;
190
191             GMutexHolder lock(view.threading.mutex);
192
193             view.gst.context = GST_GL_CONTEXT(gst_object_ref(initializeContext.context));
194             view.gst.display = GST_GL_DISPLAY(gst_object_ref(initializeContext.display));
195
196             view.wpe.width = initializeContext.width;
197             view.wpe.height = initializeContext.height;
198
199             EGLDisplay eglDisplay = gst_gl_display_egl_get_from_native(
200                 GST_GL_DISPLAY_TYPE_WAYLAND,
201                 gst_gl_display_get_handle(initializeContext.display));
202             GST_DEBUG("eglDisplay %p", eglDisplay);
203             wpe_fdo_initialize_for_egl_display(eglDisplay);
204
205             view.wpe.exportable = wpe_view_backend_exportable_fdo_egl_create(&s_exportableClient,
206                 &view, view.wpe.width, view.wpe.height);
207             auto* viewBackend = webkit_web_view_backend_new(
208                 wpe_view_backend_exportable_fdo_get_view_backend(view.wpe.exportable), nullptr, nullptr);
209
210             view.webkit.view = WEBKIT_WEB_VIEW(g_object_new(WEBKIT_TYPE_WEB_VIEW,
211                 "backend", viewBackend, nullptr));
212
213             gst_wpe_src_configure_web_view(initializeContext.src, view.webkit.view);
214
215             g_signal_connect(view.webkit.view, "load-changed", G_CALLBACK(s_loadEvent), &view);
216
217             const gchar* location;
218             gboolean drawBackground = TRUE;
219             g_object_get(initializeContext.src, "location", &location, "draw-background", &drawBackground, nullptr);
220             if (!location)
221                 g_warning("Invalid location");
222             else {
223                 view.setDrawBackground(drawBackground);
224                 view.loadUriUnlocked(location);
225             }
226             g_cond_signal(&view.threading.cond);
227             return G_SOURCE_REMOVE;
228         },
229         &initializeContext, nullptr);
230     g_source_set_priority(source, WPE_GLIB_SOURCE_PRIORITY);
231
232     {
233         GMutexHolder lock(threading.mutex);
234         g_source_attach(source, glib.context);
235         g_cond_wait(&threading.cond, &threading.mutex);
236     }
237
238     g_source_unref(source);
239
240     {
241         GST_DEBUG("waiting load to finish");
242         GMutexHolder lock(threading.ready_mutex);
243         g_cond_wait(&threading.ready_cond, &threading.ready_mutex);
244         GST_DEBUG("done");
245     }
246 }
247
248 GstEGLImage* WPEThreadedView::image()
249 {
250     GstEGLImage* ret = nullptr;
251     GMutexHolder lock(images.mutex);
252
253     GST_TRACE("pending %" GST_PTR_FORMAT " committed %" GST_PTR_FORMAT, images.pending, images.committed);
254
255     if (images.pending) {
256         auto* previousImage = images.committed;
257         images.committed = images.pending;
258         images.pending = nullptr;
259
260         frameComplete();
261
262         if (previousImage)
263             gst_egl_image_unref(previousImage);
264     }
265
266     if (images.committed)
267         ret = images.committed;
268
269     return ret;
270 }
271
272 void WPEThreadedView::resize(int width, int height)
273 {
274     GST_DEBUG("resize");
275
276     GSource* source = g_idle_source_new();
277     g_source_set_callback(source,
278         [](gpointer data) -> gboolean {
279             auto& view = *static_cast<WPEThreadedView*>(data);
280             GMutexHolder lock(view.threading.mutex);
281
282             GST_DEBUG("dispatching");
283             if (view.wpe.exportable && wpe_view_backend_exportable_fdo_get_view_backend(view.wpe.exportable))
284                 wpe_view_backend_dispatch_set_size(wpe_view_backend_exportable_fdo_get_view_backend(view.wpe.exportable), view.wpe.width, view.wpe.height);
285
286             g_cond_signal(&view.threading.cond);
287             return G_SOURCE_REMOVE;
288         },
289         this, nullptr);
290     g_source_set_priority(source, WPE_GLIB_SOURCE_PRIORITY);
291
292     {
293         GMutexHolder lock(threading.mutex);
294         g_source_attach(source, glib.context);
295         g_cond_wait(&threading.cond, &threading.mutex);
296     }
297
298     g_source_unref(source);
299 }
300
301 void WPEThreadedView::frameComplete()
302 {
303     GST_DEBUG("frame complete");
304
305     GSource* source = g_idle_source_new();
306     g_source_set_callback(source,
307         [](gpointer data) -> gboolean {
308             auto& view = *static_cast<WPEThreadedView*>(data);
309             GMutexHolder lock(view.threading.mutex);
310
311             GST_DEBUG("dispatching");
312             wpe_view_backend_exportable_fdo_dispatch_frame_complete(view.wpe.exportable);
313
314             g_cond_signal(&view.threading.cond);
315             return G_SOURCE_REMOVE;
316         },
317         this, nullptr);
318     g_source_set_priority(source, WPE_GLIB_SOURCE_PRIORITY);
319
320     {
321         GMutexHolder lock(threading.mutex);
322         g_source_attach(source, glib.context);
323         g_cond_wait(&threading.cond, &threading.mutex);
324     }
325
326     g_source_unref(source);
327 }
328
329 void WPEThreadedView::loadUriUnlocked(const gchar* uri)
330 {
331     if (webkit.uri)
332         g_free(webkit.uri);
333     webkit.uri = g_strdup(uri);
334     webkit_web_view_load_uri(webkit.view, webkit.uri);
335 }
336
337 void WPEThreadedView::loadUri(const gchar* uri)
338 {
339     GST_DEBUG("loading %s", uri);
340
341     struct UriContext {
342         WPEThreadedView& view;
343         const gchar* uri;
344     } uriContext{ *this, uri };
345
346     GSource* source = g_idle_source_new();
347     g_source_set_callback(source,
348         [](gpointer data) -> gboolean {
349             GST_DEBUG("on view thread");
350             auto& uriContext = *static_cast<UriContext*>(data);
351             auto& view = uriContext.view;
352             GMutexHolder lock(view.threading.mutex);
353
354             view.loadUriUnlocked(uriContext.uri);
355
356             g_cond_signal(&view.threading.cond);
357             return G_SOURCE_REMOVE;
358         },
359         &uriContext, nullptr);
360     g_source_set_priority(source, WPE_GLIB_SOURCE_PRIORITY);
361
362     {
363         GMutexHolder lock(threading.mutex);
364         g_source_attach(source, glib.context);
365         g_cond_wait(&threading.cond, &threading.mutex);
366         GST_DEBUG("done");
367     }
368
369     g_source_unref(source);
370 }
371
372 void WPEThreadedView::setDrawBackground(gboolean drawsBackground)
373 {
374 #if WEBKIT_CHECK_VERSION(2, 23, 0)
375     GST_DEBUG("%s background rendering", drawsBackground ? "Enabling" : "Disabling");
376     WebKitColor color;
377     webkit_color_parse(&color, drawsBackground ? "white" : "transparent");
378     webkit_web_view_set_background_color(webkit.view, &color);
379 #else
380     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());
381 #endif
382 }
383
384 void WPEThreadedView::releaseImage(EGLImageKHR image)
385 {
386     struct ReleaseImageContext {
387         WPEThreadedView& view;
388         EGLImageKHR image;
389     } releaseImageContext{ *this, image };
390
391     GSource* source = g_idle_source_new();
392     g_source_set_callback(source,
393         [](gpointer data) -> gboolean {
394             auto& releaseImageContext = *static_cast<ReleaseImageContext*>(data);
395             auto& view = releaseImageContext.view;
396             GMutexHolder lock(view.threading.mutex);
397
398             wpe_view_backend_exportable_fdo_egl_dispatch_release_image(
399                 releaseImageContext.view.wpe.exportable, releaseImageContext.image);
400
401             g_cond_signal(&view.threading.cond);
402             return G_SOURCE_REMOVE;
403         },
404         &releaseImageContext, nullptr);
405     g_source_set_priority(source, WPE_GLIB_SOURCE_PRIORITY);
406
407     {
408         GMutexHolder lock(threading.mutex);
409         g_source_attach(source, glib.context);
410         g_cond_wait(&threading.cond, &threading.mutex);
411     }
412
413     g_source_unref(source);
414 }
415
416 struct wpe_view_backend_exportable_fdo_egl_client WPEThreadedView::s_exportableClient = {
417     // export_buffer_resource
418     [](void* data, EGLImageKHR image) {
419         auto& view = *static_cast<WPEThreadedView*>(data);
420         auto* gstImage = gst_egl_image_new_wrapped(view.gst.context, image,
421             GST_GL_RGBA, &view, s_releaseImage);
422         GMutexHolder lock(view.images.mutex);
423
424         view.images.pending = gstImage;
425     },
426     // padding
427     nullptr, nullptr, nullptr, nullptr
428 };
429
430 void WPEThreadedView::s_releaseImage(GstEGLImage* image, gpointer data)
431 {
432     auto& view = *static_cast<WPEThreadedView*>(data);
433     GST_DEBUG("view %p image %" GST_PTR_FORMAT, &view, image);
434     view.releaseImage(gst_egl_image_get_image(image));
435 }