1/*
2 * Copyright (C) 2012 Igalia, S.L.
3 *
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser 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 * Lesser General Public License for more details.
13 *
14 * You should have received a copy of the GNU Lesser General Public
15 * License along with this library; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17 */
18
19#include "config.h"
20#include "GLContextEGL.h"
21
22#if USE(EGL)
23
24#include "GraphicsContext3D.h"
25#include "PlatformDisplay.h"
26
27#if USE(LIBEPOXY)
28#include "EpoxyEGL.h"
29#else
30#include <EGL/egl.h>
31#include <EGL/eglext.h>
32#endif
33
34#if USE(CAIRO)
35#include <cairo.h>
36#endif
37
38#if USE(LIBEPOXY)
39#include <epoxy/gl.h>
40#elif USE(OPENGL_ES)
41#include <GLES2/gl2.h>
42#include <GLES2/gl2ext.h>
43#else
44#include "OpenGLShims.h"
45#endif
46
47#if ENABLE(ACCELERATED_2D_CANVAS)
48// cairo-gl.h includes some definitions from GLX that conflict with
49// the ones provided by us. Since GLContextEGL doesn't use any GLX
50// functions we can safely disable them.
51#undef CAIRO_HAS_GLX_FUNCTIONS
52#include <cairo-gl.h>
53#endif
54
55#include <wtf/Vector.h>
56
57namespace WebCore {
58
59#if USE(OPENGL_ES)
60static const EGLenum gEGLAPIVersion = EGL_OPENGL_ES_API;
61#else
62static const EGLenum gEGLAPIVersion = EGL_OPENGL_API;
63#endif
64
65const char* GLContextEGL::errorString(int statusCode)
66{
67 static_assert(sizeof(int) >= sizeof(EGLint), "EGLint must not be wider than int");
68 switch (statusCode) {
69#define CASE_RETURN_STRING(name) case name: return #name
70 // https://www.khronos.org/registry/EGL/sdk/docs/man/html/eglGetError.xhtml
71 CASE_RETURN_STRING(EGL_SUCCESS);
72 CASE_RETURN_STRING(EGL_NOT_INITIALIZED);
73 CASE_RETURN_STRING(EGL_BAD_ACCESS);
74 CASE_RETURN_STRING(EGL_BAD_ALLOC);
75 CASE_RETURN_STRING(EGL_BAD_ATTRIBUTE);
76 CASE_RETURN_STRING(EGL_BAD_CONTEXT);
77 CASE_RETURN_STRING(EGL_BAD_CONFIG);
78 CASE_RETURN_STRING(EGL_BAD_CURRENT_SURFACE);
79 CASE_RETURN_STRING(EGL_BAD_DISPLAY);
80 CASE_RETURN_STRING(EGL_BAD_SURFACE);
81 CASE_RETURN_STRING(EGL_BAD_MATCH);
82 CASE_RETURN_STRING(EGL_BAD_PARAMETER);
83 CASE_RETURN_STRING(EGL_BAD_NATIVE_PIXMAP);
84 CASE_RETURN_STRING(EGL_BAD_NATIVE_WINDOW);
85 CASE_RETURN_STRING(EGL_CONTEXT_LOST);
86#undef CASE_RETURN_STRING
87 default: return "Unknown EGL error";
88 }
89}
90
91const char* GLContextEGL::lastErrorString()
92{
93 return errorString(eglGetError());
94}
95
96bool GLContextEGL::getEGLConfig(EGLDisplay display, EGLConfig* config, EGLSurfaceType surfaceType)
97{
98 std::array<EGLint, 4> rgbaSize = { 8, 8, 8, 8 };
99 if (const char* environmentVariable = getenv("WEBKIT_EGL_PIXEL_LAYOUT")) {
100 if (!strcmp(environmentVariable, "RGB565"))
101 rgbaSize = { 5, 6, 5, 0 };
102 else
103 WTFLogAlways("Unknown pixel layout %s, falling back to RGBA8888", environmentVariable);
104 }
105
106 EGLint attributeList[] = {
107#if USE(OPENGL_ES)
108 EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
109#else
110 EGL_RENDERABLE_TYPE, EGL_OPENGL_BIT,
111#endif
112 EGL_RED_SIZE, rgbaSize[0],
113 EGL_GREEN_SIZE, rgbaSize[1],
114 EGL_BLUE_SIZE, rgbaSize[2],
115 EGL_ALPHA_SIZE, rgbaSize[3],
116 EGL_STENCIL_SIZE, 8,
117 EGL_SURFACE_TYPE, EGL_NONE,
118 EGL_NONE
119 };
120
121 switch (surfaceType) {
122 case GLContextEGL::PbufferSurface:
123 attributeList[13] = EGL_PBUFFER_BIT;
124 break;
125 case GLContextEGL::PixmapSurface:
126 attributeList[13] = EGL_PIXMAP_BIT;
127 break;
128 case GLContextEGL::WindowSurface:
129 case GLContextEGL::Surfaceless:
130 attributeList[13] = EGL_WINDOW_BIT;
131 break;
132 }
133
134 EGLint count;
135 if (!eglChooseConfig(display, attributeList, nullptr, 0, &count))
136 return false;
137
138 EGLint numberConfigsReturned;
139 Vector<EGLConfig> configs(count);
140 if (!eglChooseConfig(display, attributeList, reinterpret_cast<EGLConfig*>(configs.data()), count, &numberConfigsReturned) || !numberConfigsReturned)
141 return false;
142
143 auto index = configs.findMatching([&](EGLConfig value) {
144 EGLint redSize, greenSize, blueSize, alphaSize;
145 eglGetConfigAttrib(display, value, EGL_RED_SIZE, &redSize);
146 eglGetConfigAttrib(display, value, EGL_GREEN_SIZE, &greenSize);
147 eglGetConfigAttrib(display, value, EGL_BLUE_SIZE, &blueSize);
148 eglGetConfigAttrib(display, value, EGL_ALPHA_SIZE, &alphaSize);
149 return redSize == rgbaSize[0] && greenSize == rgbaSize[1]
150 && blueSize == rgbaSize[2] && alphaSize == rgbaSize[3];
151 });
152
153 if (index != notFound) {
154 *config = configs[index];
155 return true;
156 }
157 return false;
158}
159
160std::unique_ptr<GLContextEGL> GLContextEGL::createWindowContext(GLNativeWindowType window, PlatformDisplay& platformDisplay, EGLContext sharingContext)
161{
162 EGLDisplay display = platformDisplay.eglDisplay();
163 EGLConfig config;
164 if (!getEGLConfig(display, &config, WindowSurface)) {
165 WTFLogAlways("Cannot obtain EGL window context configuration: %s\n", lastErrorString());
166 return nullptr;
167 }
168
169 EGLContext context = createContextForEGLVersion(platformDisplay, config, sharingContext);
170 if (context == EGL_NO_CONTEXT) {
171 WTFLogAlways("Cannot create EGL window context: %s\n", lastErrorString());
172 return nullptr;
173 }
174
175 EGLSurface surface = EGL_NO_SURFACE;
176#if PLATFORM(GTK)
177#if PLATFORM(X11)
178 if (platformDisplay.type() == PlatformDisplay::Type::X11)
179 surface = createWindowSurfaceX11(display, config, window);
180#endif
181#if PLATFORM(WAYLAND)
182 if (platformDisplay.type() == PlatformDisplay::Type::Wayland)
183 surface = createWindowSurfaceWayland(display, config, window);
184#endif
185#elif PLATFORM(WPE)
186 if (platformDisplay.type() == PlatformDisplay::Type::WPE)
187 surface = createWindowSurfaceWPE(display, config, window);
188#else
189 surface = eglCreateWindowSurface(display, config, static_cast<EGLNativeWindowType>(window), nullptr);
190#endif
191 if (surface == EGL_NO_SURFACE) {
192 WTFLogAlways("Cannot create EGL window surface: %s\n", lastErrorString());
193 eglDestroyContext(display, context);
194 return nullptr;
195 }
196
197 return std::unique_ptr<GLContextEGL>(new GLContextEGL(platformDisplay, context, surface, WindowSurface));
198}
199
200std::unique_ptr<GLContextEGL> GLContextEGL::createPbufferContext(PlatformDisplay& platformDisplay, EGLContext sharingContext)
201{
202 EGLDisplay display = platformDisplay.eglDisplay();
203 EGLConfig config;
204 if (!getEGLConfig(display, &config, PbufferSurface)) {
205 WTFLogAlways("Cannot obtain EGL Pbuffer configuration: %s\n", lastErrorString());
206 return nullptr;
207 }
208
209 EGLContext context = createContextForEGLVersion(platformDisplay, config, sharingContext);
210 if (context == EGL_NO_CONTEXT) {
211 WTFLogAlways("Cannot create EGL Pbuffer context: %s\n", lastErrorString());
212 return nullptr;
213 }
214
215 static const int pbufferAttributes[] = { EGL_WIDTH, 1, EGL_HEIGHT, 1, EGL_NONE };
216 EGLSurface surface = eglCreatePbufferSurface(display, config, pbufferAttributes);
217 if (surface == EGL_NO_SURFACE) {
218 WTFLogAlways("Cannot create EGL Pbuffer surface: %s\n", lastErrorString());
219 eglDestroyContext(display, context);
220 return nullptr;
221 }
222
223 return std::unique_ptr<GLContextEGL>(new GLContextEGL(platformDisplay, context, surface, PbufferSurface));
224}
225
226std::unique_ptr<GLContextEGL> GLContextEGL::createSurfacelessContext(PlatformDisplay& platformDisplay, EGLContext sharingContext)
227{
228 EGLDisplay display = platformDisplay.eglDisplay();
229 if (display == EGL_NO_DISPLAY) {
230 WTFLogAlways("Cannot create surfaceless EGL context: invalid display (last error: %s)\n", lastErrorString());
231 return nullptr;
232 }
233
234 const char* extensions = eglQueryString(display, EGL_EXTENSIONS);
235 if (!GLContext::isExtensionSupported(extensions, "EGL_KHR_surfaceless_context") && !GLContext::isExtensionSupported(extensions, "EGL_KHR_surfaceless_opengl"))
236 return nullptr;
237
238 EGLConfig config;
239 if (!getEGLConfig(display, &config, Surfaceless)) {
240 WTFLogAlways("Cannot obtain EGL surfaceless configuration: %s\n", lastErrorString());
241 return nullptr;
242 }
243
244 EGLContext context = createContextForEGLVersion(platformDisplay, config, sharingContext);
245 if (context == EGL_NO_CONTEXT) {
246 WTFLogAlways("Cannot create EGL surfaceless context: %s\n", lastErrorString());
247 return nullptr;
248 }
249
250 return std::unique_ptr<GLContextEGL>(new GLContextEGL(platformDisplay, context, EGL_NO_SURFACE, Surfaceless));
251}
252
253std::unique_ptr<GLContextEGL> GLContextEGL::createContext(GLNativeWindowType window, PlatformDisplay& platformDisplay)
254{
255 if (platformDisplay.eglDisplay() == EGL_NO_DISPLAY) {
256 WTFLogAlways("Cannot create EGL context: invalid display (last error: %s)\n", lastErrorString());
257 return nullptr;
258 }
259
260 if (eglBindAPI(gEGLAPIVersion) == EGL_FALSE) {
261#if USE(OPENGL_ES)
262 WTFLogAlways("Cannot create EGL context: error binding OpenGL ES API (%s)\n", lastErrorString());
263#else
264 WTFLogAlways("Cannot create EGL context: error binding OpenGL API (%s)\n", lastErrorString());
265#endif
266 return nullptr;
267 }
268
269 EGLContext eglSharingContext = platformDisplay.sharingGLContext() ? static_cast<GLContextEGL*>(platformDisplay.sharingGLContext())->m_context : EGL_NO_CONTEXT;
270 auto context = window ? createWindowContext(window, platformDisplay, eglSharingContext) : nullptr;
271 if (!context)
272 context = createSurfacelessContext(platformDisplay, eglSharingContext);
273 if (!context) {
274#if PLATFORM(X11)
275 if (platformDisplay.type() == PlatformDisplay::Type::X11)
276 context = createPixmapContext(platformDisplay, eglSharingContext);
277#endif
278#if PLATFORM(WAYLAND)
279 if (platformDisplay.type() == PlatformDisplay::Type::Wayland)
280 context = createWaylandContext(platformDisplay, eglSharingContext);
281#endif
282#if PLATFORM(WPE)
283 if (platformDisplay.type() == PlatformDisplay::Type::WPE)
284 context = createWPEContext(platformDisplay, eglSharingContext);
285#endif
286 }
287 if (!context)
288 context = createPbufferContext(platformDisplay, eglSharingContext);
289
290 return context;
291}
292
293std::unique_ptr<GLContextEGL> GLContextEGL::createSharingContext(PlatformDisplay& platformDisplay)
294{
295 if (platformDisplay.eglDisplay() == EGL_NO_DISPLAY) {
296 WTFLogAlways("Cannot create EGL sharing context: invalid display (last error: %s)", lastErrorString());
297 return nullptr;
298 }
299
300 if (eglBindAPI(gEGLAPIVersion) == EGL_FALSE) {
301#if USE(OPENGL_ES)
302 WTFLogAlways("Cannot create EGL sharing context: error binding OpenGL ES API (%s)\n", lastErrorString());
303#else
304 WTFLogAlways("Cannot create EGL sharing context: error binding OpenGL API (%s)\n", lastErrorString());
305#endif
306 return nullptr;
307 }
308
309 auto context = createSurfacelessContext(platformDisplay);
310 if (!context) {
311#if PLATFORM(X11)
312 if (platformDisplay.type() == PlatformDisplay::Type::X11)
313 context = createPixmapContext(platformDisplay);
314#endif
315#if PLATFORM(WAYLAND)
316 if (platformDisplay.type() == PlatformDisplay::Type::Wayland)
317 context = createWaylandContext(platformDisplay);
318#endif
319#if PLATFORM(WPE)
320 if (platformDisplay.type() == PlatformDisplay::Type::WPE)
321 context = createWPEContext(platformDisplay);
322#endif
323 }
324 if (!context)
325 context = createPbufferContext(platformDisplay);
326
327 return context;
328}
329
330GLContextEGL::GLContextEGL(PlatformDisplay& display, EGLContext context, EGLSurface surface, EGLSurfaceType type)
331 : GLContext(display)
332 , m_context(context)
333 , m_surface(surface)
334 , m_type(type)
335{
336 ASSERT(type != PixmapSurface);
337 ASSERT(type == Surfaceless || surface != EGL_NO_SURFACE);
338 RELEASE_ASSERT(m_display.eglDisplay() != EGL_NO_DISPLAY);
339 RELEASE_ASSERT(context != EGL_NO_CONTEXT);
340}
341
342GLContextEGL::~GLContextEGL()
343{
344#if USE(CAIRO)
345 if (m_cairoDevice)
346 cairo_device_destroy(m_cairoDevice);
347#endif
348
349 EGLDisplay display = m_display.eglDisplay();
350 if (m_context) {
351 glBindFramebuffer(GL_FRAMEBUFFER, 0);
352 eglMakeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
353 eglDestroyContext(display, m_context);
354 }
355
356 if (m_surface)
357 eglDestroySurface(display, m_surface);
358
359#if PLATFORM(WAYLAND)
360 destroyWaylandWindow();
361#endif
362#if PLATFORM(WPE)
363 destroyWPETarget();
364#endif
365}
366
367bool GLContextEGL::canRenderToDefaultFramebuffer()
368{
369 return m_type == WindowSurface;
370}
371
372IntSize GLContextEGL::defaultFrameBufferSize()
373{
374 if (!canRenderToDefaultFramebuffer())
375 return IntSize();
376
377 EGLDisplay display = m_display.eglDisplay();
378 EGLint width, height;
379 if (!eglQuerySurface(display, m_surface, EGL_WIDTH, &width)
380 || !eglQuerySurface(display, m_surface, EGL_HEIGHT, &height))
381 return IntSize();
382
383 return IntSize(width, height);
384}
385
386EGLContext GLContextEGL::createContextForEGLVersion(PlatformDisplay& platformDisplay, EGLConfig config, EGLContext sharingContext)
387{
388 static EGLint contextAttributes[7];
389 static bool contextAttributesInitialized = false;
390
391 if (!contextAttributesInitialized) {
392 contextAttributesInitialized = true;
393
394#if USE(OPENGL_ES)
395 // GLES case. Not much to do here besides requesting a GLES2 version.
396 contextAttributes[0] = EGL_CONTEXT_CLIENT_VERSION;
397 contextAttributes[1] = 2;
398 contextAttributes[2] = EGL_NONE;
399#else
400 // OpenGL case. We want to request an OpenGL version >= 3.2 with a core profile. If that's not possible,
401 // we'll use whatever is available. In order to request a concrete version of OpenGL we need EGL version
402 // 1.5 or EGL version 1.4 with the extension EGL_KHR_create_context.
403 EGLContext context = EGL_NO_CONTEXT;
404
405 if (platformDisplay.eglCheckVersion(1, 5)) {
406 contextAttributes[0] = EGL_CONTEXT_MAJOR_VERSION;
407 contextAttributes[1] = 3;
408 contextAttributes[2] = EGL_CONTEXT_MINOR_VERSION;
409 contextAttributes[3] = 2;
410 contextAttributes[4] = EGL_CONTEXT_OPENGL_PROFILE_MASK;
411 contextAttributes[5] = EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT;
412 contextAttributes[6] = EGL_NONE;
413
414 // Try to create a context with this configuration.
415 context = eglCreateContext(platformDisplay.eglDisplay(), config, sharingContext, contextAttributes);
416 } else if (platformDisplay.eglCheckVersion(1, 4)) {
417 const char* extensions = eglQueryString(platformDisplay.eglDisplay(), EGL_EXTENSIONS);
418 if (GLContext::isExtensionSupported(extensions, "EGL_KHR_create_context")) {
419 contextAttributes[0] = EGL_CONTEXT_MAJOR_VERSION_KHR;
420 contextAttributes[1] = 3;
421 contextAttributes[2] = EGL_CONTEXT_MINOR_VERSION_KHR;
422 contextAttributes[3] = 2;
423 contextAttributes[4] = EGL_CONTEXT_OPENGL_PROFILE_MASK_KHR;
424 contextAttributes[5] = EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT_KHR;
425 contextAttributes[6] = EGL_NONE;
426
427 // Try to create a context with this configuration.
428 context = eglCreateContext(platformDisplay.eglDisplay(), config, sharingContext, contextAttributes);
429 }
430 }
431
432 // If the context creation worked, just return it.
433 if (context != EGL_NO_CONTEXT)
434 return context;
435
436 // Legacy case: the required EGL version is not present, or we haven't been able to create a >= 3.2 OpenGL
437 // context, so just request whatever is available.
438 contextAttributes[0] = EGL_NONE;
439#endif
440 }
441
442 return eglCreateContext(platformDisplay.eglDisplay(), config, sharingContext, contextAttributes);
443}
444
445bool GLContextEGL::makeContextCurrent()
446{
447 ASSERT(m_context);
448
449 GLContext::makeContextCurrent();
450 if (eglGetCurrentContext() == m_context)
451 return true;
452
453 return eglMakeCurrent(m_display.eglDisplay(), m_surface, m_surface, m_context);
454}
455
456void GLContextEGL::swapBuffers()
457{
458 ASSERT(m_surface);
459 eglSwapBuffers(m_display.eglDisplay(), m_surface);
460}
461
462void GLContextEGL::waitNative()
463{
464 eglWaitNative(EGL_CORE_NATIVE_ENGINE);
465}
466
467void GLContextEGL::swapInterval(int interval)
468{
469 ASSERT(m_surface);
470 eglSwapInterval(m_display.eglDisplay(), interval);
471}
472
473#if USE(CAIRO)
474cairo_device_t* GLContextEGL::cairoDevice()
475{
476 if (m_cairoDevice)
477 return m_cairoDevice;
478
479#if ENABLE(ACCELERATED_2D_CANVAS)
480 m_cairoDevice = cairo_egl_device_create(m_display.eglDisplay(), m_context);
481#endif
482
483 return m_cairoDevice;
484}
485#endif
486
487#if ENABLE(GRAPHICS_CONTEXT_3D)
488PlatformGraphicsContext3D GLContextEGL::platformContext()
489{
490 return m_context;
491}
492#endif
493
494} // namespace WebCore
495
496#endif // USE(EGL)
497