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