[WPE] Add a MiniBrowser and use it to run WebDriver tests
[WebKit-https.git] / Tools / wpe / backends / HeadlessViewBackend.cpp
1 /*
2  * Copyright (C) 2016 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 INC. AND ITS CONTRIBUTORS ``AS IS''
14  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23  * THE POSSIBILITY OF SUCH DAMAGE.
24  */
25
26 #include "HeadlessViewBackend.h"
27
28 #include <cassert>
29 #include <fcntl.h>
30 #include <unistd.h>
31
32 // This include order is necessary to enforce the GBM EGL platform.
33 #include <gbm.h>
34 #include <epoxy/egl.h>
35 #include <wpe/fdo-egl.h>
36
37 #ifndef EGL_WL_bind_wayland_display
38 #define EGL_WL_bind_wayland_display 1
39 typedef EGLBoolean (EGLAPIENTRYP PFNEGLQUERYWAYLANDBUFFERWL) (EGLDisplay dpy, struct wl_resource* buffer, EGLint attribute, EGLint* value);
40
41 #define EGL_WAYLAND_BUFFER_WL 0x31D5 // eglCreateImageKHR target
42 #define EGL_WAYLAND_PLANE_WL  0x31D6 // eglCreateImageKHR target
43 #endif
44
45 namespace WPEToolingBackends {
46
47 static PFNEGLCREATEIMAGEKHRPROC createImage;
48 static PFNEGLDESTROYIMAGEKHRPROC destroyImage;
49 static PFNEGLQUERYWAYLANDBUFFERWL queryBuffer;
50 static PFNGLEGLIMAGETARGETTEXTURE2DOESPROC imageTargetTexture2DOES;
51
52 // Keep this in sync with wtf/glib/RunLoopSourcePriority.h.
53 static int kRunLoopSourcePriorityDispatcher = -70;
54
55 static EGLDisplay getEGLDisplay()
56 {
57     static EGLDisplay s_display = EGL_NO_DISPLAY;
58     if (s_display == EGL_NO_DISPLAY) {
59         EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
60         if (display == EGL_NO_DISPLAY)
61             return EGL_NO_DISPLAY;
62
63         s_display = display;
64     }
65
66     return s_display;
67 }
68
69 HeadlessViewBackend::HeadlessViewBackend(uint32_t width, uint32_t height)
70     : ViewBackend(width, height)
71 {
72     m_eglDisplay = getEGLDisplay();
73     if (!initialize())
74         return;
75
76     if (!eglMakeCurrent(m_eglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, m_eglContext))
77         return;
78
79     createImage = reinterpret_cast<PFNEGLCREATEIMAGEKHRPROC>(eglGetProcAddress("eglCreateImageKHR"));
80     destroyImage = reinterpret_cast<PFNEGLDESTROYIMAGEKHRPROC>(eglGetProcAddress("eglDestroyImageKHR"));
81     queryBuffer = reinterpret_cast<PFNEGLQUERYWAYLANDBUFFERWL>(eglGetProcAddress("eglQueryWaylandBufferWL"));
82     imageTargetTexture2DOES = reinterpret_cast<PFNGLEGLIMAGETARGETTEXTURE2DOESPROC>(eglGetProcAddress("glEGLImageTargetTexture2DOES"));
83
84     m_updateSource = g_timeout_source_new(m_frameRate / 1000);
85     g_source_set_callback(m_updateSource, [](gpointer data) -> gboolean {
86         static_cast<HeadlessViewBackend*>(data)->performUpdate();
87         return TRUE;
88     }, this, nullptr);
89     g_source_set_priority(m_updateSource, kRunLoopSourcePriorityDispatcher);
90     g_source_attach(m_updateSource, g_main_context_default());
91 }
92
93 HeadlessViewBackend::~HeadlessViewBackend()
94 {
95     if (m_updateSource) {
96         g_source_destroy(m_updateSource);
97         g_source_unref(m_updateSource);
98     }
99
100     if (auto image = std::get<0>(m_pendingImage.second))
101         destroyImage(m_eglDisplay, image);
102     if (auto image = std::get<0>(m_lockedImage.second))
103         destroyImage(m_eglDisplay, image);
104 }
105
106 cairo_surface_t* HeadlessViewBackend::createSnapshot()
107 {
108     if (!m_eglContext)
109         return nullptr;
110
111     performUpdate();
112
113     EGLImageKHR image = std::get<0>(m_lockedImage.second);
114     if (!image)
115         return nullptr;
116
117     uint32_t width = std::get<1>(m_lockedImage.second);
118     uint32_t height = std::get<2>(m_lockedImage.second);
119
120     uint8_t* buffer = new uint8_t[4 * width * height];
121     bool successfulSnapshot = false;
122
123     if (!eglMakeCurrent(m_eglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, m_eglContext))
124         return nullptr;
125
126     GLuint imageTexture;
127     glGenTextures(1, &imageTexture);
128     glBindTexture(GL_TEXTURE_2D, imageTexture);
129     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
130     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
131     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
132     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
133     glTexImage2D(GL_TEXTURE_2D, 0, GL_BGRA_EXT, width, height, 0, GL_BGRA_EXT, GL_UNSIGNED_BYTE, nullptr);
134
135     imageTargetTexture2DOES(GL_TEXTURE_2D, image);
136     glBindTexture(GL_TEXTURE_2D, 0);
137
138     GLuint imageFramebuffer;
139     glGenFramebuffers(1, &imageFramebuffer);
140     glBindFramebuffer(GL_FRAMEBUFFER, imageFramebuffer);
141     glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, imageTexture, 0);
142
143     glFlush();
144     if (glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE) {
145         glReadPixels(0, 0, width, height, GL_BGRA_EXT, GL_UNSIGNED_BYTE, buffer);
146         successfulSnapshot = true;
147     }
148
149     glBindFramebuffer(GL_FRAMEBUFFER, 0);
150     glDeleteFramebuffers(1, &imageFramebuffer);
151     glDeleteTextures(1, &imageTexture);
152
153     if (!successfulSnapshot) {
154         delete[] buffer;
155         return nullptr;
156     }
157
158     cairo_surface_t* imageSurface = cairo_image_surface_create_for_data(buffer,
159         CAIRO_FORMAT_ARGB32, width, height, cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, width));
160     cairo_surface_mark_dirty(imageSurface);
161
162     static cairo_user_data_key_t bufferKey;
163     cairo_surface_set_user_data(imageSurface, &bufferKey, buffer,
164         [](void* data) {
165             auto* buffer = static_cast<uint8_t*>(data);
166             delete[] buffer;
167         });
168
169     return imageSurface;
170 }
171
172 void HeadlessViewBackend::performUpdate()
173 {
174     if (!m_pendingImage.first)
175         return;
176
177     wpe_view_backend_exportable_fdo_dispatch_frame_complete(m_exportable);
178     if (m_lockedImage.first) {
179         wpe_view_backend_exportable_fdo_dispatch_release_buffer(m_exportable, m_lockedImage.first);
180         destroyImage(m_eglDisplay, std::get<0>(m_lockedImage.second));
181     }
182
183     m_lockedImage = m_pendingImage;
184     m_pendingImage = std::pair<struct wl_resource*, std::tuple<EGLImageKHR, uint32_t, uint32_t>> { };
185 }
186
187 void HeadlessViewBackend::displayBuffer(struct wl_resource* bufferResource)
188 {
189     if (m_pendingImage.first)
190         std::abort();
191
192     EGLint format = 0;
193     if (!queryBuffer(m_eglDisplay, bufferResource, EGL_TEXTURE_FORMAT, &format) || format != EGL_TEXTURE_RGBA)
194         return;
195
196     EGLint width, height;
197     if (!queryBuffer(m_eglDisplay, bufferResource, EGL_WIDTH, &width)
198         || !queryBuffer(m_eglDisplay, bufferResource, EGL_HEIGHT, &height))
199         return;
200
201     EGLint attributes[] = { EGL_WAYLAND_PLANE_WL, 0, EGL_NONE };
202     EGLImageKHR image = createImage(m_eglDisplay, EGL_NO_CONTEXT, EGL_WAYLAND_BUFFER_WL, bufferResource, attributes);
203     m_pendingImage = { bufferResource, std::make_tuple(image, width, height) };
204 }
205
206 } // namespace WPEToolingBackends