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:
GMutexHolder(GMutex & mutex)32     GMutexHolder(GMutex& mutex)
33         : m(mutex)
34     {
35         g_mutex_lock(&m);
36     }
~GMutexHolder()37     ~GMutexHolder()
38     {
39         g_mutex_unlock(&m);
40     }
41 
42 private:
43     GMutex& m;
44 };
45 
WPEThreadedView()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 
~WPEThreadedView()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 
s_viewThread(gpointer data)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 
backend() const149 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 
s_loadEvent(WebKitWebView *,WebKitLoadEvent event,gpointer data)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 
initialize(GstWpeSrc * src,GstGLContext * context,GstGLDisplay * display,int width,int height)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(1, 2, 0)
171             wpe_loader_init("libWPEBackend-fdo-1.0.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* wpeViewBackend = wpe_view_backend_exportable_fdo_get_view_backend(view.wpe.exportable);
208             auto* viewBackend = webkit_web_view_backend_new(wpeViewBackend, nullptr, nullptr);
209 #if defined(WPE_BACKEND_CHECK_VERSION) && WPE_BACKEND_CHECK_VERSION(1, 1, 0)
210             wpe_view_backend_add_activity_state(wpeViewBackend, wpe_view_activity_state_visible | wpe_view_activity_state_focused | wpe_view_activity_state_in_window);
211 #endif
212 
213             view.webkit.view = WEBKIT_WEB_VIEW(g_object_new(WEBKIT_TYPE_WEB_VIEW,
214                 "backend", viewBackend, nullptr));
215 
216             gst_wpe_src_configure_web_view(initializeContext.src, view.webkit.view);
217 
218             g_signal_connect(view.webkit.view, "load-changed", G_CALLBACK(s_loadEvent), &view);
219 
220             const gchar* location;
221             gboolean drawBackground = TRUE;
222             g_object_get(initializeContext.src, "location", &location, "draw-background", &drawBackground, nullptr);
223             if (!location)
224                 g_warning("Invalid location");
225             else {
226                 view.setDrawBackground(drawBackground);
227                 view.loadUriUnlocked(location);
228             }
229             g_cond_signal(&view.threading.cond);
230             return G_SOURCE_REMOVE;
231         },
232         &initializeContext, nullptr);
233     g_source_set_priority(source, WPE_GLIB_SOURCE_PRIORITY);
234 
235     {
236         GMutexHolder lock(threading.mutex);
237         g_source_attach(source, glib.context);
238         g_cond_wait(&threading.cond, &threading.mutex);
239     }
240 
241     g_source_unref(source);
242 
243     {
244         GST_DEBUG("waiting load to finish");
245         GMutexHolder lock(threading.ready_mutex);
246         g_cond_wait(&threading.ready_cond, &threading.ready_mutex);
247         GST_DEBUG("done");
248     }
249 }
250 
image()251 GstEGLImage* WPEThreadedView::image()
252 {
253     GstEGLImage* ret = nullptr;
254     GMutexHolder lock(images.mutex);
255 
256     GST_TRACE("pending %" GST_PTR_FORMAT " committed %" GST_PTR_FORMAT, images.pending, images.committed);
257 
258     if (images.pending) {
259         auto* previousImage = images.committed;
260         images.committed = images.pending;
261         images.pending = nullptr;
262 
263         frameComplete();
264 
265         if (previousImage)
266             gst_egl_image_unref(previousImage);
267     }
268 
269     if (images.committed)
270         ret = images.committed;
271 
272     return ret;
273 }
274 
resize(int width,int height)275 void WPEThreadedView::resize(int width, int height)
276 {
277     GST_DEBUG("resize");
278 
279     GSource* source = g_idle_source_new();
280     g_source_set_callback(source,
281         [](gpointer data) -> gboolean {
282             auto& view = *static_cast<WPEThreadedView*>(data);
283             GMutexHolder lock(view.threading.mutex);
284 
285             GST_DEBUG("dispatching");
286             if (view.wpe.exportable && wpe_view_backend_exportable_fdo_get_view_backend(view.wpe.exportable))
287                 wpe_view_backend_dispatch_set_size(wpe_view_backend_exportable_fdo_get_view_backend(view.wpe.exportable), view.wpe.width, view.wpe.height);
288 
289             g_cond_signal(&view.threading.cond);
290             return G_SOURCE_REMOVE;
291         },
292         this, nullptr);
293     g_source_set_priority(source, WPE_GLIB_SOURCE_PRIORITY);
294 
295     {
296         GMutexHolder lock(threading.mutex);
297         g_source_attach(source, glib.context);
298         g_cond_wait(&threading.cond, &threading.mutex);
299     }
300 
301     g_source_unref(source);
302 }
303 
frameComplete()304 void WPEThreadedView::frameComplete()
305 {
306     GST_DEBUG("frame complete");
307 
308     GSource* source = g_idle_source_new();
309     g_source_set_callback(source,
310         [](gpointer data) -> gboolean {
311             auto& view = *static_cast<WPEThreadedView*>(data);
312             GMutexHolder lock(view.threading.mutex);
313 
314             GST_DEBUG("dispatching");
315             wpe_view_backend_exportable_fdo_dispatch_frame_complete(view.wpe.exportable);
316 
317             g_cond_signal(&view.threading.cond);
318             return G_SOURCE_REMOVE;
319         },
320         this, nullptr);
321     g_source_set_priority(source, WPE_GLIB_SOURCE_PRIORITY);
322 
323     {
324         GMutexHolder lock(threading.mutex);
325         g_source_attach(source, glib.context);
326         g_cond_wait(&threading.cond, &threading.mutex);
327     }
328 
329     g_source_unref(source);
330 }
331 
loadUriUnlocked(const gchar * uri)332 void WPEThreadedView::loadUriUnlocked(const gchar* uri)
333 {
334     if (webkit.uri)
335         g_free(webkit.uri);
336 
337     GST_DEBUG("loading %s", uri);
338     webkit.uri = g_strdup(uri);
339     webkit_web_view_load_uri(webkit.view, webkit.uri);
340 }
341 
loadUri(const gchar * uri)342 void WPEThreadedView::loadUri(const gchar* uri)
343 {
344     struct UriContext {
345         WPEThreadedView& view;
346         const gchar* uri;
347     } uriContext{ *this, uri };
348 
349     GSource* source = g_idle_source_new();
350     g_source_set_callback(source,
351         [](gpointer data) -> gboolean {
352             GST_DEBUG("on view thread");
353             auto& uriContext = *static_cast<UriContext*>(data);
354             auto& view = uriContext.view;
355             GMutexHolder lock(view.threading.mutex);
356 
357             view.loadUriUnlocked(uriContext.uri);
358 
359             g_cond_signal(&view.threading.cond);
360             return G_SOURCE_REMOVE;
361         },
362         &uriContext, nullptr);
363     g_source_set_priority(source, WPE_GLIB_SOURCE_PRIORITY);
364 
365     {
366         GMutexHolder lock(threading.mutex);
367         g_source_attach(source, glib.context);
368         g_cond_wait(&threading.cond, &threading.mutex);
369         GST_DEBUG("done");
370     }
371 
372     g_source_unref(source);
373 }
374 
setDrawBackground(gboolean drawsBackground)375 void WPEThreadedView::setDrawBackground(gboolean drawsBackground)
376 {
377 #if WEBKIT_CHECK_VERSION(2, 23, 0)
378     GST_DEBUG("%s background rendering", drawsBackground ? "Enabling" : "Disabling");
379     WebKitColor color;
380     webkit_color_parse(&color, drawsBackground ? "white" : "transparent");
381     webkit_web_view_set_background_color(webkit.view, &color);
382 #else
383     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());
384 #endif
385 }
386 
releaseImage(EGLImageKHR image)387 void WPEThreadedView::releaseImage(EGLImageKHR image)
388 {
389     struct ReleaseImageContext {
390         WPEThreadedView& view;
391         EGLImageKHR image;
392     } releaseImageContext{ *this, image };
393 
394     GSource* source = g_idle_source_new();
395     g_source_set_callback(source,
396         [](gpointer data) -> gboolean {
397             auto& releaseImageContext = *static_cast<ReleaseImageContext*>(data);
398             auto& view = releaseImageContext.view;
399             GMutexHolder lock(view.threading.mutex);
400 
401             wpe_view_backend_exportable_fdo_egl_dispatch_release_image(
402                 releaseImageContext.view.wpe.exportable, releaseImageContext.image);
403 
404             g_cond_signal(&view.threading.cond);
405             return G_SOURCE_REMOVE;
406         },
407         &releaseImageContext, nullptr);
408     g_source_set_priority(source, WPE_GLIB_SOURCE_PRIORITY);
409 
410     {
411         GMutexHolder lock(threading.mutex);
412         g_source_attach(source, glib.context);
413         g_cond_wait(&threading.cond, &threading.mutex);
414     }
415 
416     g_source_unref(source);
417 }
418 
419 struct wpe_view_backend_exportable_fdo_egl_client WPEThreadedView::s_exportableClient = {
420     // export_buffer_resource
__anond28931750802(void* data, EGLImageKHR image) 421     [](void* data, EGLImageKHR image) {
422         auto& view = *static_cast<WPEThreadedView*>(data);
423         auto* gstImage = gst_egl_image_new_wrapped(view.gst.context, image,
424             GST_GL_RGBA, &view, s_releaseImage);
425         GMutexHolder lock(view.images.mutex);
426 
427         view.images.pending = gstImage;
428     },
429     // padding
430     nullptr, nullptr, nullptr, nullptr
431 };
432 
s_releaseImage(GstEGLImage * image,gpointer data)433 void WPEThreadedView::s_releaseImage(GstEGLImage* image, gpointer data)
434 {
435     auto& view = *static_cast<WPEThreadedView*>(data);
436     GST_DEBUG("view %p image %" GST_PTR_FORMAT, &view, image);
437     view.releaseImage(gst_egl_image_get_image(image));
438 }
439