1/*
2 * Copyright (C) 2016, 2019 Igalia S.L.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 * 1. Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
12 *
13 * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
14 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR
17 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24 */
25
26#include "config.h"
27#include "AcceleratedBackingStoreWayland.h"
28
29#if PLATFORM(WAYLAND) && USE(EGL)
30
31#include "LayerTreeContext.h"
32#include "WebPageProxy.h"
33// These includes need to be in this order because wayland-egl.h defines WL_EGL_PLATFORM
34// and eglplatform.h, included by egl.h, checks that to decide whether it's Wayland platform.
35#include <gdk/gdkwayland.h>
36#include <EGL/egl.h>
37#include <EGL/eglext.h>
38#include <WebCore/CairoUtilities.h>
39#include <WebCore/GLContext.h>
40
41#if USE(OPENGL_ES)
42#include <GLES2/gl2.h>
43#include <GLES2/gl2ext.h>
44#include <WebCore/Extensions3DOpenGLES.h>
45#else
46#include <WebCore/Extensions3DOpenGL.h>
47#include <WebCore/OpenGLShims.h>
48#endif
49
50#if USE(WPE_RENDERER)
51#include <wpe/fdo-egl.h>
52#else
53#include "WaylandCompositor.h"
54#endif
55
56#if USE(WPE_RENDERER)
57#if !defined(PFNGLEGLIMAGETARGETTEXTURE2DOESPROC)
58typedef void (*PFNGLEGLIMAGETARGETTEXTURE2DOESPROC) (GLenum target, GLeglImageOES);
59#endif
60
61static PFNGLEGLIMAGETARGETTEXTURE2DOESPROC glImageTargetTexture2D;
62#endif
63
64namespace WebKit {
65using namespace WebCore;
66
67std::unique_ptr<AcceleratedBackingStoreWayland> AcceleratedBackingStoreWayland::create(WebPageProxy& webPage)
68{
69#if USE(WPE_RENDERER)
70 if (!glImageTargetTexture2D) {
71 if (!wpe_fdo_initialize_for_egl_display(PlatformDisplay::sharedDisplay().eglDisplay()))
72 return nullptr;
73
74 std::unique_ptr<WebCore::GLContext> eglContext = GLContext::createOffscreenContext();
75 if (!eglContext)
76 return nullptr;
77
78 if (!eglContext->makeContextCurrent())
79 return nullptr;
80
81#if USE(OPENGL_ES)
82 std::unique_ptr<Extensions3DOpenGLES> glExtensions = std::make_unique<Extensions3DOpenGLES>(nullptr, false);
83#else
84 std::unique_ptr<Extensions3DOpenGL> glExtensions = std::make_unique<Extensions3DOpenGL>(nullptr, GLContext::current()->version() >= 320);
85#endif
86 if (glExtensions->supports("GL_OES_EGL_image") || glExtensions->supports("GL_OES_EGL_image_external"))
87 glImageTargetTexture2D = reinterpret_cast<PFNGLEGLIMAGETARGETTEXTURE2DOESPROC>(eglGetProcAddress("glEGLImageTargetTexture2DOES"));
88 }
89
90 if (!glImageTargetTexture2D) {
91 WTFLogAlways("AcceleratedBackingStoreWPE requires glEGLImageTargetTexture2D.");
92 return nullptr;
93 }
94#else
95 if (!WaylandCompositor::singleton().isRunning())
96 return nullptr;
97#endif
98 return std::unique_ptr<AcceleratedBackingStoreWayland>(new AcceleratedBackingStoreWayland(webPage));
99}
100
101AcceleratedBackingStoreWayland::AcceleratedBackingStoreWayland(WebPageProxy& webPage)
102 : AcceleratedBackingStore(webPage)
103{
104#if USE(WPE_RENDERER)
105 static struct wpe_view_backend_exportable_fdo_egl_client exportableClient = {
106 // export_egl_image
107 nullptr,
108 // export_fdo_egl_image
109 [](void* data, struct wpe_fdo_egl_exported_image* image)
110 {
111 static_cast<AcceleratedBackingStoreWayland*>(data)->displayBuffer(image);
112 },
113 // padding
114 nullptr, nullptr, nullptr
115 };
116
117 auto viewSize = webPage.viewSize();
118 m_exportable = wpe_view_backend_exportable_fdo_egl_create(&exportableClient, this, viewSize.width(), viewSize.height());
119 wpe_view_backend_initialize(wpe_view_backend_exportable_fdo_get_view_backend(m_exportable));
120#else
121 WaylandCompositor::singleton().registerWebPage(m_webPage);
122#endif
123}
124
125AcceleratedBackingStoreWayland::~AcceleratedBackingStoreWayland()
126{
127#if USE(WPE_RENDERER)
128 if (m_pendingImage)
129 wpe_view_backend_exportable_fdo_egl_dispatch_release_exported_image(m_exportable, m_pendingImage);
130 if (m_committedImage)
131 wpe_view_backend_exportable_fdo_egl_dispatch_release_exported_image(m_exportable, m_committedImage);
132 if (m_viewTexture) {
133 if (makeContextCurrent())
134 glDeleteTextures(1, &m_viewTexture);
135 }
136 wpe_view_backend_exportable_fdo_destroy(m_exportable);
137#else
138 WaylandCompositor::singleton().unregisterWebPage(m_webPage);
139#endif
140
141#if GTK_CHECK_VERSION(3, 16, 0)
142 if (m_gdkGLContext && m_gdkGLContext.get() == gdk_gl_context_get_current())
143 gdk_gl_context_clear_current();
144#endif
145}
146
147void AcceleratedBackingStoreWayland::tryEnsureGLContext()
148{
149 if (m_glContextInitialized)
150 return;
151
152 m_glContextInitialized = true;
153
154#if GTK_CHECK_VERSION(3, 16, 0)
155 GUniqueOutPtr<GError> error;
156 m_gdkGLContext = adoptGRef(gdk_window_create_gl_context(gtk_widget_get_window(m_webPage.viewWidget()), &error.outPtr()));
157 if (m_gdkGLContext) {
158#if USE(OPENGL_ES)
159 gdk_gl_context_set_use_es(m_gdkGLContext.get(), TRUE);
160#endif
161 return;
162 }
163
164 g_warning("GDK is not able to create a GL context, falling back to glReadPixels (slow!): %s", error->message);
165#endif
166
167 m_glContext = GLContext::createOffscreenContext();
168}
169
170bool AcceleratedBackingStoreWayland::makeContextCurrent()
171{
172 tryEnsureGLContext();
173
174#if GTK_CHECK_VERSION(3, 16, 0)
175 if (m_gdkGLContext) {
176 gdk_gl_context_make_current(m_gdkGLContext.get());
177 return true;
178 }
179#endif
180
181 return m_glContext ? m_glContext->makeContextCurrent() : false;
182}
183
184#if USE(WPE_RENDERER)
185void AcceleratedBackingStoreWayland::update(const LayerTreeContext& context)
186{
187 if (m_surfaceID == context.contextID)
188 return;
189
190 m_surfaceID = context.contextID;
191 if (m_pendingImage) {
192 wpe_view_backend_exportable_fdo_dispatch_frame_complete(m_exportable);
193 wpe_view_backend_exportable_fdo_egl_dispatch_release_exported_image(m_exportable, m_pendingImage);
194 m_pendingImage = nullptr;
195 }
196}
197
198int AcceleratedBackingStoreWayland::renderHostFileDescriptor()
199{
200 return wpe_view_backend_get_renderer_host_fd(wpe_view_backend_exportable_fdo_get_view_backend(m_exportable));
201}
202
203void AcceleratedBackingStoreWayland::displayBuffer(struct wpe_fdo_egl_exported_image* image)
204{
205 if (!m_surfaceID) {
206 wpe_view_backend_exportable_fdo_dispatch_frame_complete(m_exportable);
207 if (image != m_committedImage)
208 wpe_view_backend_exportable_fdo_egl_dispatch_release_exported_image(m_exportable, image);
209 return;
210 }
211
212 if (!m_viewTexture) {
213 if (!makeContextCurrent())
214 return;
215
216 glGenTextures(1, &m_viewTexture);
217 glBindTexture(GL_TEXTURE_2D, m_viewTexture);
218 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
219 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
220 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
221 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
222 }
223
224 if (m_pendingImage)
225 wpe_view_backend_exportable_fdo_egl_dispatch_release_exported_image(m_exportable, m_pendingImage);
226 m_pendingImage = image;
227
228 m_webPage.setViewNeedsDisplay(IntRect(IntPoint::zero(), m_webPage.viewSize()));
229}
230#endif
231
232bool AcceleratedBackingStoreWayland::paint(cairo_t* cr, const IntRect& clipRect)
233{
234 GLuint texture;
235 IntSize textureSize;
236
237#if USE(WPE_RENDERER)
238 if (!makeContextCurrent())
239 return false;
240
241 if (m_pendingImage) {
242 wpe_view_backend_exportable_fdo_dispatch_frame_complete(m_exportable);
243
244 if (m_committedImage)
245 wpe_view_backend_exportable_fdo_egl_dispatch_release_exported_image(m_exportable, m_committedImage);
246 m_committedImage = m_pendingImage;
247 m_pendingImage = nullptr;
248 }
249
250 if (!m_committedImage)
251 return true;
252
253 glBindTexture(GL_TEXTURE_2D, m_viewTexture);
254 glImageTargetTexture2D(GL_TEXTURE_2D, wpe_fdo_egl_exported_image_get_egl_image(m_committedImage));
255
256 texture = m_viewTexture;
257 textureSize = { static_cast<int>(wpe_fdo_egl_exported_image_get_width(m_committedImage)), static_cast<int>(wpe_fdo_egl_exported_image_get_height(m_committedImage)) };
258#else
259 if (!WaylandCompositor::singleton().getTexture(m_webPage, texture, textureSize))
260 return false;
261#endif
262
263 cairo_save(cr);
264
265#if GTK_CHECK_VERSION(3, 16, 0)
266 if (m_gdkGLContext) {
267 gdk_cairo_draw_from_gl(cr, gtk_widget_get_window(m_webPage.viewWidget()), texture, GL_TEXTURE, m_webPage.deviceScaleFactor(), 0, 0, textureSize.width(), textureSize.height());
268 cairo_restore(cr);
269 return true;
270 }
271#endif
272
273 ASSERT(m_glContext);
274
275 if (!m_surface || cairo_image_surface_get_width(m_surface.get()) != textureSize.width() || cairo_image_surface_get_height(m_surface.get()) != textureSize.height())
276 m_surface = adoptRef(cairo_image_surface_create(CAIRO_FORMAT_ARGB32, textureSize.width(), textureSize.height()));
277
278 cairoSurfaceSetDeviceScale(m_surface.get(), m_webPage.deviceScaleFactor(), m_webPage.deviceScaleFactor());
279
280 GLuint fb;
281 glGenFramebuffers(1, &fb);
282 glBindFramebuffer(GL_FRAMEBUFFER, fb);
283 glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0);
284
285 glPixelStorei(GL_PACK_ALIGNMENT, 4);
286
287#if USE(OPENGL_ES)
288 unsigned char* data = cairo_image_surface_get_data(m_surface.get());
289 if (cairo_image_surface_get_stride(m_surface.get()) == textureSize.width() * 4)
290 glReadPixels(0, 0, textureSize.width(), textureSize.height(), GL_RGBA, GL_UNSIGNED_BYTE, data);
291 else {
292 int strideBytes = cairo_image_surface_get_stride(m_surface.get());
293 for (int i = 0; i < textureSize.height(); i++) {
294 unsigned char* dataOffset = data + i * strideBytes;
295 glReadPixels(0, i, textureSize.width(), 1, GL_RGBA, GL_UNSIGNED_BYTE, dataOffset);
296 }
297 }
298
299 // Convert to BGRA.
300 int totalBytes = textureSize.width() * textureSize.height() * 4;
301 for (int i = 0; i < totalBytes; i += 4)
302 std::swap(data[i], data[i + 2]);
303#else
304 glPixelStorei(GL_PACK_ROW_LENGTH, cairo_image_surface_get_stride(m_surface.get()) / 4);
305 glReadPixels(0, 0, textureSize.width(), textureSize.height(), GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, cairo_image_surface_get_data(m_surface.get()));
306 glPixelStorei(GL_PACK_ROW_LENGTH, 0);
307#endif
308
309 glBindFramebuffer(GL_FRAMEBUFFER, 0);
310 glDeleteFramebuffers(1, &fb);
311
312 // The surface can be modified by the web process at any time, so we mark it
313 // as dirty to ensure we always render the updated contents as soon as possible.
314 cairo_surface_mark_dirty(m_surface.get());
315
316 // The compositor renders the texture flipped for gdk_cairo_draw_from_gl, fix that here.
317 cairo_matrix_t transform;
318 cairo_matrix_init(&transform, 1, 0, 0, -1, 0, textureSize.height() / m_webPage.deviceScaleFactor());
319 cairo_transform(cr, &transform);
320
321 cairo_rectangle(cr, clipRect.x(), clipRect.y(), clipRect.width(), clipRect.height());
322 cairo_set_source_surface(cr, m_surface.get(), 0, 0);
323 cairo_set_operator(cr, CAIRO_OPERATOR_OVER);
324 cairo_fill(cr);
325
326 cairo_restore(cr);
327
328 return true;
329}
330
331} // namespace WebKit
332
333#endif // PLATFORM(WAYLAND) && USE(EGL)
334