wpe: Add support for SHM without requiring EGLDisplay
[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 #ifdef HAVE_CONFIG_H
21 #include <config.h>
22 #endif
23
24 #include "WPEThreadedView.h"
25
26 #include <gst/gl/gl.h>
27 #include <gst/gl/egl/gsteglimage.h>
28 #include <gst/gl/egl/gstgldisplay_egl.h>
29 #include <wayland-server.h>
30
31 #include <cstdio>
32 #include <mutex>
33
34 #if ENABLE_SHM_BUFFER_SUPPORT
35 #include <wpe/unstable/fdo-shm.h>
36 #endif
37
38 GST_DEBUG_CATEGORY_EXTERN (wpe_src_debug);
39 #define GST_CAT_DEFAULT wpe_src_debug
40
41 #if defined(WPE_FDO_CHECK_VERSION) && WPE_FDO_CHECK_VERSION(1, 3, 0)
42 #define USE_DEPRECATED_FDO_EGL_IMAGE 0
43 #define WPE_GLIB_SOURCE_PRIORITY G_PRIORITY_DEFAULT
44 #else
45 #define USE_DEPRECATED_FDO_EGL_IMAGE 1
46 #define WPE_GLIB_SOURCE_PRIORITY -70
47 #endif
48
49 class GMutexHolder {
50 public:
51     GMutexHolder(GMutex& mutex)
52         : m(mutex)
53     {
54         g_mutex_lock(&m);
55     }
56     ~GMutexHolder()
57     {
58         g_mutex_unlock(&m);
59     }
60
61 private:
62     GMutex& m;
63 };
64
65 WPEThreadedView::WPEThreadedView()
66 {
67     g_mutex_init(&threading.mutex);
68     g_cond_init(&threading.cond);
69     g_mutex_init(&threading.ready_mutex);
70     g_cond_init(&threading.ready_cond);
71
72     g_mutex_init(&images_mutex);
73
74     {
75         GMutexHolder lock(threading.mutex);
76         threading.thread = g_thread_new("WPEThreadedView",
77             s_viewThread, this);
78         g_cond_wait(&threading.cond, &threading.mutex);
79         GST_DEBUG("thread spawned");
80     }
81 }
82
83 WPEThreadedView::~WPEThreadedView()
84 {
85     {
86         GMutexHolder lock(images_mutex);
87
88         if (egl.pending) {
89             gst_egl_image_unref(egl.pending);
90             egl.pending = nullptr;
91         }
92         if (egl.committed) {
93             gst_egl_image_unref(egl.committed);
94             egl.committed = nullptr;
95         }
96     }
97
98     {
99         GMutexHolder lock(threading.mutex);
100         wpe_view_backend_exportable_fdo_destroy(wpe.exportable);
101     }
102
103     if (gst.display) {
104         gst_object_unref(gst.display);
105         gst.display = nullptr;
106     }
107
108     if (gst.context) {
109         gst_object_unref(gst.context);
110         gst.context = nullptr;
111     }
112
113     if (threading.thread) {
114         g_thread_unref(threading.thread);
115         threading.thread = nullptr;
116     }
117
118     g_mutex_clear(&threading.mutex);
119     g_cond_clear(&threading.cond);
120     g_mutex_clear(&threading.ready_mutex);
121     g_cond_clear(&threading.ready_cond);
122     g_mutex_clear(&images_mutex);
123 }
124
125 gpointer WPEThreadedView::s_viewThread(gpointer data)
126 {
127     auto& view = *static_cast<WPEThreadedView*>(data);
128
129     view.glib.context = g_main_context_new();
130     view.glib.loop = g_main_loop_new(view.glib.context, FALSE);
131
132     g_main_context_push_thread_default(view.glib.context);
133
134     {
135         GSource* source = g_idle_source_new();
136         g_source_set_callback(source,
137             [](gpointer data) -> gboolean {
138                 auto& view = *static_cast<WPEThreadedView*>(data);
139                 GMutexHolder lock(view.threading.mutex);
140                 g_cond_signal(&view.threading.cond);
141                 return G_SOURCE_REMOVE;
142             },
143             &view, nullptr);
144         g_source_attach(source, view.glib.context);
145         g_source_unref(source);
146     }
147
148     g_main_loop_run(view.glib.loop);
149
150     g_main_loop_unref(view.glib.loop);
151     view.glib.loop = nullptr;
152
153     if (view.webkit.view) {
154         g_object_unref(view.webkit.view);
155         view.webkit.view = nullptr;
156     }
157     if (view.webkit.uri) {
158         g_free(view.webkit.uri);
159         view.webkit.uri = nullptr;
160     }
161
162     g_main_context_pop_thread_default(view.glib.context);
163     g_main_context_unref(view.glib.context);
164     view.glib.context = nullptr;
165     return nullptr;
166 }
167
168 struct wpe_view_backend* WPEThreadedView::backend() const
169 {
170     return wpe.exportable ? wpe_view_backend_exportable_fdo_get_view_backend(wpe.exportable) : nullptr;
171 }
172
173 void WPEThreadedView::s_loadEvent(WebKitWebView*, WebKitLoadEvent event, gpointer data)
174 {
175     if (event == WEBKIT_LOAD_COMMITTED) {
176         auto& view = *static_cast<WPEThreadedView*>(data);
177         GMutexHolder lock(view.threading.ready_mutex);
178         g_cond_signal(&view.threading.ready_cond);
179     }
180 }
181
182 bool WPEThreadedView::initialize(GstWpeSrc* src, GstGLContext* context, GstGLDisplay* display, int width, int height)
183 {
184     GST_DEBUG("context %p display %p, size (%d,%d)", context, display, width, height);
185
186     static std::once_flag s_loaderFlag;
187     std::call_once(s_loaderFlag,
188         [] {
189 #if defined(WPE_BACKEND_CHECK_VERSION) && WPE_BACKEND_CHECK_VERSION(1, 2, 0)
190             wpe_loader_init("libWPEBackend-fdo-1.0.so");
191 #endif
192         });
193
194     EGLDisplay eglDisplay = EGL_NO_DISPLAY;
195     if (context && display)
196       eglDisplay = gst_gl_display_egl_get_from_native(GST_GL_DISPLAY_TYPE_WAYLAND,
197                                                       gst_gl_display_get_handle(display));
198     GST_DEBUG("eglDisplay %p", eglDisplay);
199
200     struct InitializeContext {
201         GstWpeSrc* src;
202         WPEThreadedView& view;
203         GstGLContext* context;
204         GstGLDisplay* display;
205         EGLDisplay eglDisplay;
206         int width;
207         int height;
208         bool result;
209     } initializeContext { src, *this, context, display, eglDisplay, width, height, FALSE };
210
211     GSource* source = g_idle_source_new();
212     g_source_set_callback(source,
213         [](gpointer data) -> gboolean {
214             GST_DEBUG("on view thread");
215             auto& initializeContext = *static_cast<InitializeContext*>(data);
216             auto& view = initializeContext.view;
217
218             GMutexHolder lock(view.threading.mutex);
219
220             if (initializeContext.context)
221               view.gst.context = GST_GL_CONTEXT(gst_object_ref(initializeContext.context));
222             if (initializeContext.display)
223               view.gst.display = GST_GL_DISPLAY(gst_object_ref(initializeContext.display));
224
225             view.wpe.width = initializeContext.width;
226             view.wpe.height = initializeContext.height;
227
228             if (initializeContext.eglDisplay) {
229               initializeContext.result = wpe_fdo_initialize_for_egl_display(initializeContext.eglDisplay);
230               GST_DEBUG("FDO EGL display initialisation result: %d", initializeContext.result);
231             } else {
232 #if ENABLE_SHM_BUFFER_SUPPORT
233               initializeContext.result = wpe_fdo_initialize_shm();
234               GST_DEBUG("FDO SHM initialisation result: %d", initializeContext.result);
235 #else
236               GST_WARNING("FDO SHM support is available only in WPEBackend-FDO 1.7.0");
237 #endif
238             }
239             if (!initializeContext.result) {
240               g_cond_signal(&view.threading.cond);
241               return G_SOURCE_REMOVE;
242             }
243
244             if (initializeContext.eglDisplay) {
245               view.wpe.exportable = wpe_view_backend_exportable_fdo_egl_create(&s_exportableEGLClient,
246                   &view, view.wpe.width, view.wpe.height);
247             } else {
248 #if ENABLE_SHM_BUFFER_SUPPORT
249               view.wpe.exportable = wpe_view_backend_exportable_fdo_create(&s_exportableClient,
250                   &view, view.wpe.width, view.wpe.height);
251 #endif
252             }
253             auto* wpeViewBackend = wpe_view_backend_exportable_fdo_get_view_backend(view.wpe.exportable);
254             auto* viewBackend = webkit_web_view_backend_new(wpeViewBackend, nullptr, nullptr);
255 #if defined(WPE_BACKEND_CHECK_VERSION) && WPE_BACKEND_CHECK_VERSION(1, 1, 0)
256             wpe_view_backend_add_activity_state(wpeViewBackend, wpe_view_activity_state_visible | wpe_view_activity_state_focused | wpe_view_activity_state_in_window);
257 #endif
258
259             view.webkit.view = WEBKIT_WEB_VIEW(g_object_new(WEBKIT_TYPE_WEB_VIEW,
260                 "backend", viewBackend, nullptr));
261
262             gst_wpe_src_configure_web_view(initializeContext.src, view.webkit.view);
263
264             g_signal_connect(view.webkit.view, "load-changed", G_CALLBACK(s_loadEvent), &view);
265
266             const gchar* location;
267             gboolean drawBackground = TRUE;
268             g_object_get(initializeContext.src, "location", &location, "draw-background", &drawBackground, nullptr);
269             view.setDrawBackground(drawBackground);
270             if (location)
271                 view.loadUriUnlocked(location);
272             g_cond_signal(&view.threading.cond);
273             return G_SOURCE_REMOVE;
274         },
275         &initializeContext, nullptr);
276     g_source_set_priority(source, WPE_GLIB_SOURCE_PRIORITY);
277
278     {
279         GMutexHolder lock(threading.mutex);
280         g_source_attach(source, glib.context);
281         g_cond_wait(&threading.cond, &threading.mutex);
282     }
283
284     g_source_unref(source);
285
286     if (initializeContext.result && webkit.uri) {
287         GST_DEBUG("waiting load to finish");
288         GMutexHolder lock(threading.ready_mutex);
289         g_cond_wait(&threading.ready_cond, &threading.ready_mutex);
290         GST_DEBUG("done");
291     }
292     return initializeContext.result;
293 }
294
295 GstEGLImage* WPEThreadedView::image()
296 {
297     GstEGLImage* ret = nullptr;
298     bool dispatchFrameComplete = false;
299
300     {
301         GMutexHolder lock(images_mutex);
302
303         GST_TRACE("pending %" GST_PTR_FORMAT " (%d) committed %" GST_PTR_FORMAT " (%d)", egl.pending,
304                   GST_IS_EGL_IMAGE(egl.pending) ? GST_MINI_OBJECT_REFCOUNT_VALUE(GST_MINI_OBJECT_CAST(egl.pending)) : 0,
305                   egl.committed,
306                   GST_IS_EGL_IMAGE(egl.committed) ? GST_MINI_OBJECT_REFCOUNT_VALUE(GST_MINI_OBJECT_CAST(egl.committed)) : 0);
307
308         if (egl.pending) {
309             auto* previousImage = egl.committed;
310             egl.committed = egl.pending;
311             egl.pending = nullptr;
312
313             if (previousImage)
314                 gst_egl_image_unref(previousImage);
315             dispatchFrameComplete = true;
316         }
317
318         if (egl.committed)
319             ret = egl.committed;
320     }
321
322     if (dispatchFrameComplete)
323         frameComplete();
324
325     return ret;
326 }
327
328 GstBuffer* WPEThreadedView::buffer()
329 {
330     GstBuffer* ret = nullptr;
331     bool dispatchFrameComplete = false;
332
333     {
334         GMutexHolder lock(images_mutex);
335
336         GST_TRACE("pending %" GST_PTR_FORMAT " (%d) committed %" GST_PTR_FORMAT " (%d)", shm.pending,
337                   GST_IS_BUFFER(shm.pending) ? GST_MINI_OBJECT_REFCOUNT_VALUE(GST_MINI_OBJECT_CAST(shm.pending)) : 0,
338                   shm.committed,
339                   GST_IS_BUFFER(shm.committed) ? GST_MINI_OBJECT_REFCOUNT_VALUE(GST_MINI_OBJECT_CAST(shm.committed)) : 0);
340
341         if (shm.pending) {
342             auto* previousImage = shm.committed;
343             shm.committed = shm.pending;
344             shm.pending = nullptr;
345
346             if (previousImage)
347                 gst_buffer_unref(previousImage);
348             dispatchFrameComplete = true;
349         }
350
351         if (shm.committed)
352             ret = shm.committed;
353     }
354
355     if (dispatchFrameComplete)
356         frameComplete();
357
358     return ret;
359 }
360
361 void WPEThreadedView::resize(int width, int height)
362 {
363     GST_DEBUG("resize to %dx%d", width, height);
364     wpe.width = width;
365     wpe.height = height;
366
367     GSource* source = g_idle_source_new();
368     g_source_set_callback(source,
369         [](gpointer data) -> gboolean {
370             auto& view = *static_cast<WPEThreadedView*>(data);
371             GMutexHolder lock(view.threading.mutex);
372
373             GST_DEBUG("dispatching");
374             if (view.wpe.exportable && wpe_view_backend_exportable_fdo_get_view_backend(view.wpe.exportable))
375                 wpe_view_backend_dispatch_set_size(wpe_view_backend_exportable_fdo_get_view_backend(view.wpe.exportable), view.wpe.width, view.wpe.height);
376
377             g_cond_signal(&view.threading.cond);
378             return G_SOURCE_REMOVE;
379         },
380         this, nullptr);
381     g_source_set_priority(source, WPE_GLIB_SOURCE_PRIORITY);
382
383     {
384         GMutexHolder lock(threading.mutex);
385         g_source_attach(source, glib.context);
386         g_cond_wait(&threading.cond, &threading.mutex);
387     }
388
389     g_source_unref(source);
390 }
391
392 void WPEThreadedView::frameComplete()
393 {
394     GST_TRACE("frame complete");
395
396     GSource* source = g_idle_source_new();
397     g_source_set_callback(source,
398         [](gpointer data) -> gboolean {
399             auto& view = *static_cast<WPEThreadedView*>(data);
400             GMutexHolder lock(view.threading.mutex);
401
402             GST_TRACE("dispatching");
403             wpe_view_backend_exportable_fdo_dispatch_frame_complete(view.wpe.exportable);
404
405             g_cond_signal(&view.threading.cond);
406             return G_SOURCE_REMOVE;
407         },
408         this, nullptr);
409     g_source_set_priority(source, WPE_GLIB_SOURCE_PRIORITY);
410
411     {
412         GMutexHolder lock(threading.mutex);
413         g_source_attach(source, glib.context);
414         g_cond_wait(&threading.cond, &threading.mutex);
415     }
416
417     g_source_unref(source);
418 }
419
420 void WPEThreadedView::loadUriUnlocked(const gchar* uri)
421 {
422     if (webkit.uri)
423         g_free(webkit.uri);
424
425     GST_DEBUG("loading %s", uri);
426     webkit.uri = g_strdup(uri);
427     webkit_web_view_load_uri(webkit.view, webkit.uri);
428 }
429
430 void WPEThreadedView::loadUri(const gchar* uri)
431 {
432     struct UriContext {
433         WPEThreadedView& view;
434         const gchar* uri;
435     } uriContext { *this, uri };
436
437     GSource* source = g_idle_source_new();
438     g_source_set_callback(source,
439         [](gpointer data) -> gboolean {
440             GST_DEBUG("on view thread");
441             auto& uriContext = *static_cast<UriContext*>(data);
442             auto& view = uriContext.view;
443             GMutexHolder lock(view.threading.mutex);
444
445             view.loadUriUnlocked(uriContext.uri);
446
447             g_cond_signal(&view.threading.cond);
448             return G_SOURCE_REMOVE;
449         },
450         &uriContext, nullptr);
451     g_source_set_priority(source, WPE_GLIB_SOURCE_PRIORITY);
452
453     {
454         GMutexHolder lock(threading.mutex);
455         g_source_attach(source, glib.context);
456         g_cond_wait(&threading.cond, &threading.mutex);
457         GST_DEBUG("done");
458     }
459
460     g_source_unref(source);
461 }
462
463 void WPEThreadedView::loadData(GBytes* bytes)
464 {
465     struct DataContext {
466         WPEThreadedView& view;
467         GBytes* bytes;
468     } dataContext { *this, g_bytes_ref(bytes) };
469
470     GSource* source = g_idle_source_new();
471     g_source_set_callback(source,
472         [](gpointer data) -> gboolean {
473             GST_DEBUG("on view thread");
474             auto& dataContext = *static_cast<DataContext*>(data);
475             auto& view = dataContext.view;
476             GMutexHolder lock(view.threading.mutex);
477
478             webkit_web_view_load_bytes(view.webkit.view, dataContext.bytes, nullptr, nullptr, nullptr);
479             g_bytes_unref(dataContext.bytes);
480
481             g_cond_signal(&view.threading.cond);
482             return G_SOURCE_REMOVE;
483         },
484         &dataContext, nullptr);
485     g_source_set_priority(source, WPE_GLIB_SOURCE_PRIORITY);
486
487     {
488         GMutexHolder lock(threading.mutex);
489         g_source_attach(source, glib.context);
490         g_cond_wait(&threading.cond, &threading.mutex);
491         GST_DEBUG("done");
492     }
493
494     g_source_unref(source);
495 }
496
497 void WPEThreadedView::setDrawBackground(gboolean drawsBackground)
498 {
499 #if WEBKIT_CHECK_VERSION(2, 24, 0)
500     GST_DEBUG("%s background rendering", drawsBackground ? "Enabling" : "Disabling");
501     WebKitColor color;
502     webkit_color_parse(&color, drawsBackground ? "white" : "transparent");
503     webkit_web_view_set_background_color(webkit.view, &color);
504 #else
505     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());
506 #endif
507 }
508
509 void WPEThreadedView::releaseImage(gpointer imagePointer)
510 {
511     struct ReleaseImageContext {
512         WPEThreadedView& view;
513         gpointer imagePointer;
514     } releaseImageContext{ *this, imagePointer };
515
516     GSource* source = g_idle_source_new();
517     g_source_set_callback(source,
518         [](gpointer data) -> gboolean {
519             auto& releaseImageContext = *static_cast<ReleaseImageContext*>(data);
520             auto& view = releaseImageContext.view;
521             GMutexHolder lock(view.threading.mutex);
522
523             GST_TRACE("Dispatch release exported image %p", releaseImageContext.imagePointer);
524 #if USE_DEPRECATED_FDO_EGL_IMAGE
525             wpe_view_backend_exportable_fdo_egl_dispatch_release_image(releaseImageContext.view.wpe.exportable,
526                 static_cast<EGLImageKHR>(releaseImageContext.imagePointer));
527 #else
528             wpe_view_backend_exportable_fdo_egl_dispatch_release_exported_image(releaseImageContext.view.wpe.exportable,
529                 static_cast<struct wpe_fdo_egl_exported_image*>(releaseImageContext.imagePointer));
530 #endif
531             g_cond_signal(&view.threading.cond);
532             return G_SOURCE_REMOVE;
533         },
534         &releaseImageContext, nullptr);
535     g_source_set_priority(source, WPE_GLIB_SOURCE_PRIORITY);
536
537     {
538         GMutexHolder lock(threading.mutex);
539         g_source_attach(source, glib.context);
540         g_cond_wait(&threading.cond, &threading.mutex);
541     }
542
543     g_source_unref(source);
544 }
545
546 struct ImageContext {
547     WPEThreadedView* view;
548     gpointer image;
549 };
550
551 void WPEThreadedView::handleExportedImage(gpointer image)
552 {
553     ImageContext* imageContext = g_slice_new(ImageContext);
554     imageContext->view = this;
555     imageContext->image = static_cast<gpointer>(image);
556     EGLImageKHR eglImage;
557 #if USE_DEPRECATED_FDO_EGL_IMAGE
558     eglImage = static_cast<EGLImageKHR>(image);
559 #else
560     eglImage = wpe_fdo_egl_exported_image_get_egl_image(static_cast<struct wpe_fdo_egl_exported_image*>(image));
561 #endif
562
563     auto* gstImage = gst_egl_image_new_wrapped(gst.context, eglImage, GST_GL_RGBA, imageContext, s_releaseImage);
564     {
565       GMutexHolder lock(images_mutex);
566
567       GST_TRACE("EGLImage %p wrapped in GstEGLImage %" GST_PTR_FORMAT, eglImage, gstImage);
568       egl.pending = gstImage;
569     }
570 }
571
572 #if ENABLE_SHM_BUFFER_SUPPORT
573 struct SHMBufferContext {
574   WPEThreadedView* view;
575   struct wpe_fdo_shm_exported_buffer* buffer;
576 };
577
578 void WPEThreadedView::releaseSHMBuffer(gpointer data)
579 {
580     SHMBufferContext* context = static_cast<SHMBufferContext*>(data);
581     struct ReleaseBufferContext {
582         WPEThreadedView& view;
583         SHMBufferContext* context;
584     } releaseImageContext{ *this, context };
585
586     GSource* source = g_idle_source_new();
587     g_source_set_callback(source,
588         [](gpointer data) -> gboolean {
589             auto& releaseBufferContext = *static_cast<ReleaseBufferContext*>(data);
590             auto& view = releaseBufferContext.view;
591             GMutexHolder lock(view.threading.mutex);
592
593             struct wpe_fdo_shm_exported_buffer* buffer = static_cast<struct wpe_fdo_shm_exported_buffer*>(releaseBufferContext.context->buffer);
594             GST_TRACE("Dispatch release exported buffer %p", buffer);
595             wpe_view_backend_exportable_fdo_dispatch_release_shm_exported_buffer(view.wpe.exportable, buffer);
596             g_cond_signal(&view.threading.cond);
597             return G_SOURCE_REMOVE;
598         },
599         &releaseImageContext, nullptr);
600     g_source_set_priority(source, WPE_GLIB_SOURCE_PRIORITY);
601
602     {
603         GMutexHolder lock(threading.mutex);
604         g_source_attach(source, glib.context);
605         g_cond_wait(&threading.cond, &threading.mutex);
606     }
607
608     g_source_unref(source);
609 }
610
611 void WPEThreadedView::s_releaseSHMBuffer(gpointer data)
612 {
613     SHMBufferContext* context = static_cast<SHMBufferContext*>(data);
614     context->view->releaseSHMBuffer(data);
615     g_slice_free(SHMBufferContext, context);
616 }
617
618 void WPEThreadedView::handleExportedBuffer(struct wpe_fdo_shm_exported_buffer* buffer)
619 {
620     struct wl_shm_buffer* shmBuffer = wpe_fdo_shm_exported_buffer_get_shm_buffer(buffer);
621     auto format = wl_shm_buffer_get_format(shmBuffer);
622     if (format != WL_SHM_FORMAT_ARGB8888 && format != WL_SHM_FORMAT_XRGB8888) {
623         GST_ERROR("Unsupported pixel format: %d", format);
624         return;
625     }
626
627     int32_t width = wl_shm_buffer_get_width(shmBuffer);
628     int32_t height = wl_shm_buffer_get_height(shmBuffer);
629     gint stride = wl_shm_buffer_get_stride(shmBuffer);
630     gsize size = width * height * 4;
631     auto* data = static_cast<uint8_t*>(wl_shm_buffer_get_data(shmBuffer));
632
633     SHMBufferContext* bufferContext = g_slice_new(SHMBufferContext);
634     bufferContext->view = this;
635     bufferContext->buffer = buffer;
636
637     auto* gstBuffer = gst_buffer_new_wrapped_full(GST_MEMORY_FLAG_READONLY, data, size, 0, size, bufferContext, s_releaseSHMBuffer);
638     gsize offsets[1];
639     gint strides[1];
640     offsets[0] = 0;
641     strides[0] = stride;
642     gst_buffer_add_video_meta_full(gstBuffer, GST_VIDEO_FRAME_FLAG_NONE, GST_VIDEO_FORMAT_BGRA, width, height, 1, offsets, strides);
643
644     {
645         GMutexHolder lock(images_mutex);
646         GST_TRACE("SHM buffer %p wrapped in buffer %" GST_PTR_FORMAT, buffer, gstBuffer);
647         shm.pending = gstBuffer;
648     }
649 }
650 #endif
651
652 struct wpe_view_backend_exportable_fdo_egl_client WPEThreadedView::s_exportableEGLClient = {
653 #if USE_DEPRECATED_FDO_EGL_IMAGE
654     // export_egl_image
655     [](void* data, EGLImageKHR image) {
656         auto& view = *static_cast<WPEThreadedView*>(data);
657         view.handleExportedImage(static_cast<gpointer>(image));
658     },
659     nullptr, nullptr,
660 #else
661     // export_egl_image
662     nullptr,
663     [](void* data, struct wpe_fdo_egl_exported_image* image) {
664         auto& view = *static_cast<WPEThreadedView*>(data);
665         view.handleExportedImage(static_cast<gpointer>(image));
666     },
667     nullptr,
668 #endif // USE_DEPRECATED_FDO_EGL_IMAGE
669     // padding
670     nullptr, nullptr
671 };
672
673 #if ENABLE_SHM_BUFFER_SUPPORT
674 struct wpe_view_backend_exportable_fdo_client WPEThreadedView::s_exportableClient = {
675     nullptr,
676     nullptr,
677     // export_shm_buffer
678     [](void* data, struct wpe_fdo_shm_exported_buffer* buffer) {
679         auto& view = *static_cast<WPEThreadedView*>(data);
680         view.handleExportedBuffer(buffer);
681     },
682     nullptr,
683     nullptr,
684 };
685 #endif
686
687 void WPEThreadedView::s_releaseImage(GstEGLImage* image, gpointer data)
688 {
689     ImageContext* context = static_cast<ImageContext*>(data);
690     context->view->releaseImage(context->image);
691     g_slice_free(ImageContext, context);
692 }