2fd806ff4e7be84a4be8d1457336277eb5790e63
[WebKit-https.git] / Tools / WebKitTestRunner / wpe / 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 "config.h"
27 #include "HeadlessViewBackend.h"
28
29 #include <cassert>
30 #include <fcntl.h>
31 #include <unistd.h>
32 #include <wtf/glib/RunLoopSourcePriority.h>
33
34 // FIXME: Deploy good practices and clean up GBM resources at process exit.
35 static EGLDisplay getEGLDisplay()
36 {
37     static EGLDisplay s_display = EGL_NO_DISPLAY;
38     if (s_display == EGL_NO_DISPLAY) {
39         int fd = open("/dev/dri/renderD128", O_RDWR | O_CLOEXEC | O_NOCTTY | O_NONBLOCK);
40         if (fd < 0)
41             return EGL_NO_DISPLAY;
42
43         struct gbm_device* device = gbm_create_device(fd);
44         if (!device)
45             return EGL_NO_DISPLAY;
46
47         EGLDisplay display = eglGetDisplay(device);
48         if (display == EGL_NO_DISPLAY)
49             return EGL_NO_DISPLAY;
50
51         if (!eglInitialize(display, nullptr, nullptr))
52             return EGL_NO_DISPLAY;
53
54         if (!eglBindAPI(EGL_OPENGL_ES_API))
55             return EGL_NO_DISPLAY;
56
57         s_display = display;
58     }
59
60     return s_display;
61 }
62
63 HeadlessViewBackend::HeadlessViewBackend()
64 {
65     m_egl.display = getEGLDisplay();
66
67     static const EGLint configAttributes[13] = {
68         EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
69         EGL_RED_SIZE, 1,
70         EGL_GREEN_SIZE, 1,
71         EGL_BLUE_SIZE, 1,
72         EGL_ALPHA_SIZE, 1,
73         EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
74         EGL_NONE
75     };
76
77     EGLint numConfigs;
78     EGLBoolean ret = eglChooseConfig(m_egl.display, configAttributes, &m_egl.config, 1, &numConfigs);
79     if (!ret || !numConfigs)
80         return;
81
82     static const EGLint contextAttributes[3] = {
83         EGL_CONTEXT_CLIENT_VERSION, 2,
84         EGL_NONE
85     };
86
87     m_egl.context = eglCreateContext(m_egl.display, m_egl.config, EGL_NO_CONTEXT, contextAttributes);
88     if (m_egl.context == EGL_NO_CONTEXT)
89         return;
90
91     if (!eglMakeCurrent(m_egl.display, EGL_NO_SURFACE, EGL_NO_SURFACE, m_egl.context))
92         return;
93
94     m_egl.createImage = reinterpret_cast<PFNEGLCREATEIMAGEKHRPROC>(eglGetProcAddress("eglCreateImageKHR"));
95     m_egl.destroyImage = reinterpret_cast<PFNEGLDESTROYIMAGEKHRPROC>(eglGetProcAddress("eglDestroyImageKHR"));
96     m_egl.imageTargetTexture2DOES = reinterpret_cast<PFNGLEGLIMAGETARGETTEXTURE2DOESPROC>(eglGetProcAddress("glEGLImageTargetTexture2DOES"));
97
98     m_exportable = wpe_mesa_view_backend_exportable_dma_buf_create(&s_exportableClient, this);
99
100     m_updateSource = g_timeout_source_new(m_frameRate / 1000);
101     g_source_set_callback(m_updateSource,
102         [](gpointer data) -> gboolean {
103             auto& backend = *static_cast<HeadlessViewBackend*>(data);
104             backend.performUpdate();
105             return TRUE;
106         }, this, nullptr);
107     g_source_set_priority(m_updateSource, RunLoopSourcePriority::RunLoopDispatcher);
108     g_source_attach(m_updateSource, g_main_context_default());
109 }
110
111 HeadlessViewBackend::~HeadlessViewBackend()
112 {
113     if (m_updateSource)
114         g_source_destroy(m_updateSource);
115
116     if (auto image = std::get<0>(m_pendingImage.second))
117         m_egl.destroyImage(m_egl.display, image);
118     if (auto image = std::get<0>(m_lockedImage.second))
119         m_egl.destroyImage(m_egl.display, image);
120
121     for (auto it : m_exportMap) {
122         int fd = it.second;
123         if (fd >= 0)
124             close(fd);
125     }
126
127     if (m_egl.context)
128         eglDestroyContext(m_egl.display, m_egl.context);
129 }
130
131 struct wpe_view_backend* HeadlessViewBackend::backend() const
132 {
133     return wpe_mesa_view_backend_exportable_dma_buf_get_view_backend(m_exportable);
134 }
135
136 cairo_surface_t* HeadlessViewBackend::createSnapshot()
137 {
138     performUpdate();
139
140     EGLImageKHR image = std::get<0>(m_lockedImage.second);
141     if (!image)
142         return nullptr;
143
144     uint32_t width = std::get<1>(m_lockedImage.second);
145     uint32_t height = std::get<2>(m_lockedImage.second);
146
147     uint8_t* buffer = new uint8_t[4 * width * height];
148     bool successfulSnapshot = false;
149
150     makeCurrent();
151
152     GLuint imageTexture;
153     glGenTextures(1, &imageTexture);
154     glBindTexture(GL_TEXTURE_2D, imageTexture);
155     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
156     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
157     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
158     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
159     glTexImage2D(GL_TEXTURE_2D, 0, GL_BGRA_EXT, width, height, 0, GL_BGRA_EXT, GL_UNSIGNED_BYTE, nullptr);
160
161     m_egl.imageTargetTexture2DOES(GL_TEXTURE_2D, image);
162     glBindTexture(GL_TEXTURE_2D, 0);
163
164     GLuint imageFramebuffer;
165     glGenFramebuffers(1, &imageFramebuffer);
166     glBindFramebuffer(GL_FRAMEBUFFER, imageFramebuffer);
167     glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, imageTexture, 0);
168
169     glFlush();
170     if (glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE) {
171         glReadPixels(0, 0, width, height, GL_BGRA_EXT, GL_UNSIGNED_BYTE, buffer);
172         successfulSnapshot = true;
173     }
174
175     glBindFramebuffer(GL_FRAMEBUFFER, 0);
176     glDeleteFramebuffers(1, &imageFramebuffer);
177     glDeleteTextures(1, &imageTexture);
178
179     if (!successfulSnapshot) {
180         delete[] buffer;
181         return nullptr;
182     }
183
184     cairo_surface_t* imageSurface = cairo_image_surface_create_for_data(buffer,
185         CAIRO_FORMAT_ARGB32, width, height, cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, width));
186     cairo_surface_mark_dirty(imageSurface);
187
188     static cairo_user_data_key_t bufferKey;
189     cairo_surface_set_user_data(imageSurface, &bufferKey, buffer,
190         [](void* data) {
191             auto* buffer = static_cast<uint8_t*>(data);
192             delete[] buffer;
193         });
194
195     return imageSurface;
196 }
197
198 bool HeadlessViewBackend::makeCurrent()
199 {
200     return eglMakeCurrent(m_egl.display, EGL_NO_SURFACE, EGL_NO_SURFACE, m_egl.context);
201 }
202
203 void HeadlessViewBackend::performUpdate()
204 {
205     if (!m_pendingImage.first)
206         return;
207
208     wpe_mesa_view_backend_exportable_dma_buf_dispatch_frame_complete(m_exportable);
209     if (m_lockedImage.first) {
210         wpe_mesa_view_backend_exportable_dma_buf_dispatch_release_buffer(m_exportable, m_lockedImage.first);
211         m_egl.destroyImage(m_egl.display, std::get<0>(m_lockedImage.second));
212     }
213
214     m_lockedImage = m_pendingImage;
215     m_pendingImage = std::pair<uint32_t, std::tuple<EGLImageKHR, uint32_t, uint32_t>> { };
216 }
217
218 struct wpe_mesa_view_backend_exportable_dma_buf_client HeadlessViewBackend::s_exportableClient = {
219     // export_dma_buf
220     [](void* data, struct wpe_mesa_view_backend_exportable_dma_buf_data* imageData)
221     {
222         auto& backend = *static_cast<HeadlessViewBackend*>(data);
223
224         auto it = backend.m_exportMap.end();
225         if (imageData->fd >= 0) {
226             assert(backend.m_exportMap.find(imageData->handle) == backend.m_exportMap.end());
227
228             it = backend.m_exportMap.insert({ imageData->handle, imageData->fd }).first;
229         } else {
230             assert(backend.m_exportMap.find(imageData->handle) != backend.m_exportMap.end());
231             it = backend.m_exportMap.find(imageData->handle);
232         }
233
234         assert(it != backend.m_exportMap.end());
235         uint32_t handle = it->first;
236         int32_t fd = it->second;
237
238         backend.makeCurrent();
239
240         EGLint attributes[] = {
241             EGL_WIDTH, static_cast<EGLint>(imageData->width),
242             EGL_HEIGHT, static_cast<EGLint>(imageData->height),
243             EGL_LINUX_DRM_FOURCC_EXT, static_cast<EGLint>(imageData->format),
244             EGL_DMA_BUF_PLANE0_FD_EXT, fd,
245             EGL_DMA_BUF_PLANE0_OFFSET_EXT, 0,
246             EGL_DMA_BUF_PLANE0_PITCH_EXT, static_cast<EGLint>(imageData->stride),
247             EGL_NONE,
248         };
249         EGLImageKHR image = backend.m_egl.createImage(backend.m_egl.display, EGL_NO_CONTEXT, EGL_LINUX_DMA_BUF_EXT, nullptr, attributes);
250         backend.m_pendingImage = { imageData->handle, std::make_tuple(image, imageData->width, imageData->height) };
251     },
252 };