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