1/*
2 * Copyright (C) 2011, 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 "GLContextGLX.h"
21
22#if USE(GLX)
23#include "GraphicsContext3D.h"
24#include "OpenGLShims.h"
25#include "PlatformDisplayX11.h"
26#include "XErrorTrapper.h"
27#include <GL/glx.h>
28#include <cairo.h>
29
30#if ENABLE(ACCELERATED_2D_CANVAS)
31#include <cairo-gl.h>
32#endif
33
34namespace WebCore {
35
36#if !defined(PFNGLXSWAPINTERVALSGIPROC)
37typedef int (*PFNGLXSWAPINTERVALSGIPROC) (int);
38#endif
39#if !defined(PFNGLXCREATECONTEXTATTRIBSARBPROC)
40typedef GLXContext (*PFNGLXCREATECONTEXTATTRIBSARBPROC) (Display *dpy, GLXFBConfig config, GLXContext share_context, Bool direct, const int *attrib_list);
41#endif
42
43static PFNGLXSWAPINTERVALSGIPROC glXSwapIntervalSGI;
44static PFNGLXCREATECONTEXTATTRIBSARBPROC glXCreateContextAttribsARB;
45
46static bool hasSGISwapControlExtension(Display* display)
47{
48 static bool initialized = false;
49 if (initialized)
50 return !!glXSwapIntervalSGI;
51
52 initialized = true;
53 if (!GLContext::isExtensionSupported(glXQueryExtensionsString(display, 0), "GLX_SGI_swap_control"))
54 return false;
55
56 glXSwapIntervalSGI = reinterpret_cast<PFNGLXSWAPINTERVALSGIPROC>(glXGetProcAddress(reinterpret_cast<const unsigned char*>("glXSwapIntervalSGI")));
57 return !!glXSwapIntervalSGI;
58}
59
60static bool hasGLXARBCreateContextExtension(Display* display)
61{
62 static bool initialized = false;
63 if (initialized)
64 return !!glXCreateContextAttribsARB;
65
66 initialized = true;
67 if (!GLContext::isExtensionSupported(glXQueryExtensionsString(display, 0), "GLX_ARB_create_context"))
68 return false;
69
70 glXCreateContextAttribsARB = reinterpret_cast<PFNGLXCREATECONTEXTATTRIBSARBPROC>(glXGetProcAddress(reinterpret_cast<const unsigned char*>("glXCreateContextAttribsARB")));
71 return !!glXCreateContextAttribsARB;
72}
73
74static GLXContext createGLXARBContext(Display* display, GLXFBConfig config, GLXContext sharingContext)
75{
76 // We want to create a context with version >= 3.2 core profile, cause that ensures that the i965 driver won't
77 // use the software renderer. If that doesn't work, we will use whatever version available. Unfortunately,
78 // there's no way to know whether glXCreateContextAttribsARB can provide an OpenGL version >= 3.2 until
79 // we actually call it and check the return value. To make things more fun, if a version >= 3.2 cannot be
80 // provided, glXCreateContextAttribsARB will throw a GLXBadFBConfig X error, causing the app to crash.
81 // So, the first time a context is requested, we set a X error trap to disable crashes with GLXBadFBConfig
82 // and then check whether the return value is a context or not.
83
84 static bool canCreate320Context = false;
85 static bool canCreate320ContextInitialized = false;
86
87 static const int contextAttributes[] = {
88 GLX_CONTEXT_MAJOR_VERSION_ARB, 3,
89 GLX_CONTEXT_MINOR_VERSION_ARB, 2,
90 0
91 };
92
93 if (!canCreate320ContextInitialized) {
94 canCreate320ContextInitialized = true;
95
96 {
97 // Set an X error trapper that ignores errors to avoid crashing on GLXBadFBConfig. Use a scope
98 // here to limit the error trap to just this context creation call.
99 XErrorTrapper trapper(display, XErrorTrapper::Policy::Ignore);
100 GLXContext context = glXCreateContextAttribsARB(display, config, sharingContext, GL_TRUE, contextAttributes);
101 if (context) {
102 canCreate320Context = true;
103 return context;
104 }
105 }
106
107 // Creating the 3.2 context failed, so use whatever is available.
108 return glXCreateContextAttribsARB(display, config, sharingContext, GL_TRUE, nullptr);
109 }
110
111 if (canCreate320Context)
112 return glXCreateContextAttribsARB(display, config, sharingContext, GL_TRUE, contextAttributes);
113
114 return glXCreateContextAttribsARB(display, config, sharingContext, GL_TRUE, nullptr);
115}
116
117static bool compatibleVisuals(XVisualInfo* a, XVisualInfo* b)
118{
119 return a->c_class == b->c_class
120 && a->depth == b->depth
121 && a->red_mask == b->red_mask
122 && a->green_mask == b->green_mask
123 && a->blue_mask == b->blue_mask
124 && a->colormap_size == b->colormap_size
125 && a->bits_per_rgb == b->bits_per_rgb;
126}
127
128std::unique_ptr<GLContextGLX> GLContextGLX::createWindowContext(GLNativeWindowType window, PlatformDisplay& platformDisplay, GLXContext sharingContext)
129{
130 // In order to create the GLContext, we need to select a GLXFBConfig that has depth and stencil
131 // buffers that is compatible with the Visual used to create the window. To do this, we request
132 // all the GLXFBConfigs that have the features we need and compare their XVisualInfo to check whether
133 // they are compatible with the window one. Then we try to create the GLContext with each of those
134 // configs until we succeed, and finally fallback to the window config if nothing else works.
135 Display* display = downcast<PlatformDisplayX11>(platformDisplay).native();
136 XWindowAttributes attributes;
137 if (!XGetWindowAttributes(display, static_cast<Window>(window), &attributes))
138 return nullptr;
139
140 XVisualInfo visualInfo;
141 visualInfo.visualid = XVisualIDFromVisual(attributes.visual);
142
143 int numConfigs = 0;
144 GLXFBConfig windowConfig = nullptr;
145 XUniquePtr<GLXFBConfig> configs(glXGetFBConfigs(display, DefaultScreen(display), &numConfigs));
146 for (int i = 0; i < numConfigs; i++) {
147 XUniquePtr<XVisualInfo> glxVisualInfo(glXGetVisualFromFBConfig(display, configs.get()[i]));
148 if (!glxVisualInfo)
149 continue;
150 if (glxVisualInfo.get()->visualid == visualInfo.visualid) {
151 windowConfig = configs.get()[i];
152 break;
153 }
154 }
155 ASSERT(windowConfig);
156 XUniquePtr<XVisualInfo> windowVisualInfo(glXGetVisualFromFBConfig(display, windowConfig));
157
158 static const int fbConfigAttributes[] = {
159 GLX_DRAWABLE_TYPE, GLX_WINDOW_BIT,
160 GLX_RENDER_TYPE, GLX_RGBA_BIT,
161 GLX_X_RENDERABLE, GL_TRUE,
162 GLX_RED_SIZE, 1,
163 GLX_GREEN_SIZE, 1,
164 GLX_BLUE_SIZE, 1,
165 GLX_ALPHA_SIZE, 1,
166 GLX_DEPTH_SIZE, 1,
167 GLX_STENCIL_SIZE, 1,
168 GLX_DOUBLEBUFFER, GL_TRUE,
169 GLX_CONFIG_CAVEAT, GLX_NONE,
170#ifdef GLX_FRAMEBUFFER_SRGB_CAPABLE_EXT
171 // Discard sRGB configs if any sRGB extension is installed.
172 GLX_FRAMEBUFFER_SRGB_CAPABLE_EXT, GL_FALSE,
173#endif
174 0
175 };
176 configs.reset(glXChooseFBConfig(display, DefaultScreen(display), fbConfigAttributes, &numConfigs));
177 XUniqueGLXContext context;
178 for (int i = 0; i < numConfigs; i++) {
179 XUniquePtr<XVisualInfo> configVisualInfo(glXGetVisualFromFBConfig(display, configs.get()[i]));
180 if (!configVisualInfo)
181 continue;
182 if (compatibleVisuals(windowVisualInfo.get(), configVisualInfo.get())) {
183 // Try to create a context with this config. Use the trapper in case we get an XError.
184 XErrorTrapper trapper(display, XErrorTrapper::Policy::Ignore);
185 if (hasGLXARBCreateContextExtension(display))
186 context.reset(createGLXARBContext(display, configs.get()[i], sharingContext));
187 else {
188 // Legacy OpenGL version.
189 context.reset(glXCreateContext(display, configVisualInfo.get(), sharingContext, True));
190 }
191
192 if (context)
193 return std::unique_ptr<GLContextGLX>(new GLContextGLX(platformDisplay, WTFMove(context), window));
194 }
195 }
196
197 // Fallback to the config used by the window. We don't probably have the buffers we need in
198 // this config and that will cause artifacts, but it's better than not rendering anything.
199 if (hasGLXARBCreateContextExtension(display))
200 context.reset(createGLXARBContext(display, windowConfig, sharingContext));
201 else {
202 // Legacy OpenGL version.
203 context.reset(glXCreateContext(display, windowVisualInfo.get(), sharingContext, True));
204 }
205
206 if (!context)
207 return nullptr;
208
209 return std::unique_ptr<GLContextGLX>(new GLContextGLX(platformDisplay, WTFMove(context), window));
210}
211
212std::unique_ptr<GLContextGLX> GLContextGLX::createPbufferContext(PlatformDisplay& platformDisplay, GLXContext sharingContext)
213{
214 static const int fbConfigAttributes[] = {
215 GLX_DRAWABLE_TYPE, GLX_PBUFFER_BIT,
216 GLX_RENDER_TYPE, GLX_RGBA_BIT,
217 GLX_RED_SIZE, 1,
218 GLX_GREEN_SIZE, 1,
219 GLX_BLUE_SIZE, 1,
220 GLX_ALPHA_SIZE, 1,
221 GLX_DOUBLEBUFFER, GL_FALSE,
222 0
223 };
224
225 int returnedElements;
226 Display* display = downcast<PlatformDisplayX11>(platformDisplay).native();
227 XUniquePtr<GLXFBConfig> configs(glXChooseFBConfig(display, 0, fbConfigAttributes, &returnedElements));
228 if (!returnedElements)
229 return nullptr;
230
231 // We will be rendering to a texture, so our pbuffer does not need to be large.
232 static const int pbufferAttributes[] = { GLX_PBUFFER_WIDTH, 1, GLX_PBUFFER_HEIGHT, 1, 0 };
233 XUniqueGLXPbuffer pbuffer(glXCreatePbuffer(display, configs.get()[0], pbufferAttributes));
234 if (!pbuffer)
235 return nullptr;
236
237 XUniqueGLXContext context;
238 if (hasGLXARBCreateContextExtension(display))
239 context.reset(createGLXARBContext(display, configs.get()[0], sharingContext));
240 else {
241 // Legacy OpenGL version.
242 context.reset(glXCreateNewContext(display, configs.get()[0], GLX_RGBA_TYPE, sharingContext, GL_TRUE));
243 }
244
245 if (!context)
246 return nullptr;
247
248 return std::unique_ptr<GLContextGLX>(new GLContextGLX(platformDisplay, WTFMove(context), WTFMove(pbuffer)));
249}
250
251std::unique_ptr<GLContextGLX> GLContextGLX::createPixmapContext(PlatformDisplay& platformDisplay, GLXContext sharingContext)
252{
253 static int visualAttributes[] = {
254 GLX_RGBA,
255 GLX_RED_SIZE, 1,
256 GLX_GREEN_SIZE, 1,
257 GLX_BLUE_SIZE, 1,
258 GLX_ALPHA_SIZE, 1,
259 0
260 };
261
262 Display* display = downcast<PlatformDisplayX11>(platformDisplay).native();
263 XUniquePtr<XVisualInfo> visualInfo(glXChooseVisual(display, DefaultScreen(display), visualAttributes));
264 if (!visualInfo)
265 return nullptr;
266
267 XUniqueGLXContext context(glXCreateContext(display, visualInfo.get(), sharingContext, GL_TRUE));
268 if (!context)
269 return nullptr;
270
271 XUniquePixmap pixmap(XCreatePixmap(display, DefaultRootWindow(display), 1, 1, visualInfo->depth));
272 if (!pixmap)
273 return nullptr;
274
275 XUniqueGLXPixmap glxPixmap(glXCreateGLXPixmap(display, visualInfo.get(), pixmap.get()));
276 if (!glxPixmap)
277 return nullptr;
278
279 return std::unique_ptr<GLContextGLX>(new GLContextGLX(platformDisplay, WTFMove(context), WTFMove(pixmap), WTFMove(glxPixmap)));
280}
281
282std::unique_ptr<GLContextGLX> GLContextGLX::createContext(GLNativeWindowType window, PlatformDisplay& platformDisplay)
283{
284 GLXContext glxSharingContext = platformDisplay.sharingGLContext() ? static_cast<GLContextGLX*>(platformDisplay.sharingGLContext())->m_context.get() : nullptr;
285 auto context = window ? createWindowContext(window, platformDisplay, glxSharingContext) : nullptr;
286 if (!context)
287 context = createPbufferContext(platformDisplay, glxSharingContext);
288 if (!context)
289 context = createPixmapContext(platformDisplay, glxSharingContext);
290
291 return context;
292}
293
294std::unique_ptr<GLContextGLX> GLContextGLX::createSharingContext(PlatformDisplay& platformDisplay)
295{
296 auto context = createPbufferContext(platformDisplay);
297 if (!context)
298 context = createPixmapContext(platformDisplay);
299 return context;
300}
301
302GLContextGLX::GLContextGLX(PlatformDisplay& display, XUniqueGLXContext&& context, GLNativeWindowType window)
303 : GLContext(display)
304 , m_x11Display(downcast<PlatformDisplayX11>(m_display).native())
305 , m_context(WTFMove(context))
306 , m_window(static_cast<Window>(window))
307{
308}
309
310GLContextGLX::GLContextGLX(PlatformDisplay& display, XUniqueGLXContext&& context, XUniqueGLXPbuffer&& pbuffer)
311 : GLContext(display)
312 , m_x11Display(downcast<PlatformDisplayX11>(m_display).native())
313 , m_context(WTFMove(context))
314 , m_pbuffer(WTFMove(pbuffer))
315{
316}
317
318GLContextGLX::GLContextGLX(PlatformDisplay& display, XUniqueGLXContext&& context, XUniquePixmap&& pixmap, XUniqueGLXPixmap&& glxPixmap)
319 : GLContext(display)
320 , m_x11Display(downcast<PlatformDisplayX11>(m_display).native())
321 , m_context(WTFMove(context))
322 , m_pixmap(WTFMove(pixmap))
323 , m_glxPixmap(WTFMove(glxPixmap))
324{
325}
326
327GLContextGLX::~GLContextGLX()
328{
329 if (m_cairoDevice)
330 cairo_device_destroy(m_cairoDevice);
331
332 if (m_context) {
333 // Due to a bug in some nvidia drivers, we need bind the default framebuffer in a context before
334 // destroying it to avoid a crash. In order to do that, we need to make the context current and,
335 // after the bind change, we need to set the previous context again.
336 GLContext* previousActiveContext = GLContext::current();
337 makeContextCurrent();
338 glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
339 if (previousActiveContext && previousActiveContext != this) {
340 // If there was a previous context different from this one, just make it current again.
341 previousActiveContext->makeContextCurrent();
342 } else {
343 // If there was no previous context or this was the previous, set a void context as current.
344 // We use the GLX function here, and the destructor of GLContext will clean the pointer
345 // returned by GLContext::current().
346 glXMakeCurrent(m_x11Display, None, None);
347 }
348 }
349}
350
351bool GLContextGLX::canRenderToDefaultFramebuffer()
352{
353 return m_window;
354}
355
356IntSize GLContextGLX::defaultFrameBufferSize()
357{
358 if (!canRenderToDefaultFramebuffer() || !m_window)
359 return IntSize();
360
361 int x, y;
362 Window rootWindow;
363 unsigned int width, height, borderWidth, depth;
364 if (!XGetGeometry(m_x11Display, m_window, &rootWindow, &x, &y, &width, &height, &borderWidth, &depth))
365 return IntSize();
366
367 return IntSize(width, height);
368}
369
370bool GLContextGLX::makeContextCurrent()
371{
372 ASSERT(m_context && (m_window || m_pbuffer || m_glxPixmap));
373
374 GLContext::makeContextCurrent();
375 if (glXGetCurrentContext() == m_context.get())
376 return true;
377
378 if (m_window)
379 return glXMakeCurrent(m_x11Display, m_window, m_context.get());
380
381 if (m_pbuffer)
382 return glXMakeCurrent(m_x11Display, m_pbuffer.get(), m_context.get());
383
384 return ::glXMakeCurrent(m_x11Display, m_glxPixmap.get(), m_context.get());
385}
386
387void GLContextGLX::swapBuffers()
388{
389 if (m_window)
390 glXSwapBuffers(m_x11Display, m_window);
391}
392
393void GLContextGLX::waitNative()
394{
395 glXWaitX();
396}
397
398void GLContextGLX::swapInterval(int interval)
399{
400 if (!hasSGISwapControlExtension(m_x11Display))
401 return;
402 glXSwapIntervalSGI(interval);
403}
404
405cairo_device_t* GLContextGLX::cairoDevice()
406{
407 if (m_cairoDevice)
408 return m_cairoDevice;
409
410#if ENABLE(ACCELERATED_2D_CANVAS) && CAIRO_HAS_GLX_FUNCTIONS
411 m_cairoDevice = cairo_glx_device_create(m_x11Display, m_context.get());
412#endif
413
414 return m_cairoDevice;
415}
416
417#if ENABLE(GRAPHICS_CONTEXT_3D)
418PlatformGraphicsContext3D GLContextGLX::platformContext()
419{
420 return m_context.get();
421}
422#endif
423
424} // namespace WebCore
425
426#endif // USE(GLX)
427