blob: adf3219cd93cb5a86bd6a6d650e1a01020222d5f [file] [log] [blame]
/*
* Copyright (C) 2011, 2012 Igalia, S.L.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "config.h"
#include "GLContextGLX.h"
#if USE(GLX)
#include "OpenGLShims.h"
#include "PlatformDisplayX11.h"
#include "XErrorTrapper.h"
#include <GL/glx.h>
#include <cairo.h>
#if ENABLE(ACCELERATED_2D_CANVAS)
#include <cairo-gl.h>
#endif
namespace WebCore {
#if !defined(PFNGLXSWAPINTERVALSGIPROC)
typedef int (*PFNGLXSWAPINTERVALSGIPROC) (int);
#endif
#if !defined(PFNGLXCREATECONTEXTATTRIBSARBPROC)
typedef GLXContext (*PFNGLXCREATECONTEXTATTRIBSARBPROC) (Display *dpy, GLXFBConfig config, GLXContext share_context, Bool direct, const int *attrib_list);
#endif
static PFNGLXSWAPINTERVALSGIPROC glXSwapIntervalSGI;
static PFNGLXCREATECONTEXTATTRIBSARBPROC glXCreateContextAttribsARB;
static bool hasSGISwapControlExtension(Display* display)
{
static bool initialized = false;
if (initialized)
return !!glXSwapIntervalSGI;
initialized = true;
if (!GLContext::isExtensionSupported(glXQueryExtensionsString(display, 0), "GLX_SGI_swap_control"))
return false;
glXSwapIntervalSGI = reinterpret_cast<PFNGLXSWAPINTERVALSGIPROC>(glXGetProcAddress(reinterpret_cast<const unsigned char*>("glXSwapIntervalSGI")));
return !!glXSwapIntervalSGI;
}
static bool hasGLXARBCreateContextExtension(Display* display)
{
static bool initialized = false;
if (initialized)
return !!glXCreateContextAttribsARB;
initialized = true;
if (!GLContext::isExtensionSupported(glXQueryExtensionsString(display, 0), "GLX_ARB_create_context"))
return false;
glXCreateContextAttribsARB = reinterpret_cast<PFNGLXCREATECONTEXTATTRIBSARBPROC>(glXGetProcAddress(reinterpret_cast<const unsigned char*>("glXCreateContextAttribsARB")));
return !!glXCreateContextAttribsARB;
}
static GLXContext createGLXARBContext(Display* display, GLXFBConfig config, GLXContext sharingContext)
{
// We want to create a context with version >= 3.2 core profile, cause that ensures that the i965 driver won't
// use the software renderer. If that doesn't work, we will use whatever version available. Unfortunately,
// there's no way to know whether glXCreateContextAttribsARB can provide an OpenGL version >= 3.2 until
// we actually call it and check the return value. To make things more fun, if a version >= 3.2 cannot be
// provided, glXCreateContextAttribsARB will throw a GLXBadFBConfig X error, causing the app to crash.
// So, the first time a context is requested, we set a X error trap to disable crashes with GLXBadFBConfig
// and then check whether the return value is a context or not.
static bool canCreate320Context = false;
static bool canCreate320ContextInitialized = false;
static const int contextAttributes[] = {
GLX_CONTEXT_MAJOR_VERSION_ARB, 3,
GLX_CONTEXT_MINOR_VERSION_ARB, 2,
0
};
if (!canCreate320ContextInitialized) {
canCreate320ContextInitialized = true;
{
// Set an X error trapper that ignores errors to avoid crashing on GLXBadFBConfig. Use a scope
// here to limit the error trap to just this context creation call.
XErrorTrapper trapper(display, XErrorTrapper::Policy::Ignore);
GLXContext context = glXCreateContextAttribsARB(display, config, sharingContext, GL_TRUE, contextAttributes);
if (context) {
canCreate320Context = true;
return context;
}
}
// Creating the 3.2 context failed, so use whatever is available.
return glXCreateContextAttribsARB(display, config, sharingContext, GL_TRUE, nullptr);
}
if (canCreate320Context)
return glXCreateContextAttribsARB(display, config, sharingContext, GL_TRUE, contextAttributes);
return glXCreateContextAttribsARB(display, config, sharingContext, GL_TRUE, nullptr);
}
static bool compatibleVisuals(XVisualInfo* a, XVisualInfo* b)
{
return a->c_class == b->c_class
&& a->depth == b->depth
&& a->red_mask == b->red_mask
&& a->green_mask == b->green_mask
&& a->blue_mask == b->blue_mask
&& a->colormap_size == b->colormap_size
&& a->bits_per_rgb == b->bits_per_rgb;
}
std::unique_ptr<GLContextGLX> GLContextGLX::createWindowContext(GLNativeWindowType window, PlatformDisplay& platformDisplay, GLXContext sharingContext)
{
// In order to create the GLContext, we need to select a GLXFBConfig that has depth and stencil
// buffers that is compatible with the Visual used to create the window. To do this, we request
// all the GLXFBConfigs that have the features we need and compare their XVisualInfo to check whether
// they are compatible with the window one. Then we try to create the GLContext with each of those
// configs until we succeed, and finally fallback to the window config if nothing else works.
Display* display = downcast<PlatformDisplayX11>(platformDisplay).native();
XWindowAttributes attributes;
if (!XGetWindowAttributes(display, static_cast<Window>(window), &attributes))
return nullptr;
XVisualInfo visualInfo;
visualInfo.visualid = XVisualIDFromVisual(attributes.visual);
int numConfigs = 0;
GLXFBConfig windowConfig = nullptr;
XUniquePtr<GLXFBConfig> configs(glXGetFBConfigs(display, DefaultScreen(display), &numConfigs));
for (int i = 0; i < numConfigs; i++) {
XUniquePtr<XVisualInfo> glxVisualInfo(glXGetVisualFromFBConfig(display, configs.get()[i]));
if (!glxVisualInfo)
continue;
if (glxVisualInfo.get()->visualid == visualInfo.visualid) {
windowConfig = configs.get()[i];
break;
}
}
ASSERT(windowConfig);
XUniquePtr<XVisualInfo> windowVisualInfo(glXGetVisualFromFBConfig(display, windowConfig));
static const int fbConfigAttributes[] = {
GLX_DRAWABLE_TYPE, GLX_WINDOW_BIT,
GLX_RENDER_TYPE, GLX_RGBA_BIT,
GLX_X_RENDERABLE, GL_TRUE,
GLX_RED_SIZE, 1,
GLX_GREEN_SIZE, 1,
GLX_BLUE_SIZE, 1,
GLX_ALPHA_SIZE, 1,
GLX_DEPTH_SIZE, 1,
GLX_STENCIL_SIZE, 1,
GLX_DOUBLEBUFFER, GL_TRUE,
GLX_CONFIG_CAVEAT, GLX_NONE,
#ifdef GLX_FRAMEBUFFER_SRGB_CAPABLE_EXT
// Discard sRGB configs if any sRGB extension is installed.
GLX_FRAMEBUFFER_SRGB_CAPABLE_EXT, GL_FALSE,
#endif
0
};
configs.reset(glXChooseFBConfig(display, DefaultScreen(display), fbConfigAttributes, &numConfigs));
XUniqueGLXContext context;
for (int i = 0; i < numConfigs; i++) {
XUniquePtr<XVisualInfo> configVisualInfo(glXGetVisualFromFBConfig(display, configs.get()[i]));
if (!configVisualInfo)
continue;
if (compatibleVisuals(windowVisualInfo.get(), configVisualInfo.get())) {
// Try to create a context with this config. Use the trapper in case we get an XError.
XErrorTrapper trapper(display, XErrorTrapper::Policy::Ignore);
if (hasGLXARBCreateContextExtension(display))
context.reset(createGLXARBContext(display, configs.get()[i], sharingContext));
else {
// Legacy OpenGL version.
context.reset(glXCreateContext(display, configVisualInfo.get(), sharingContext, True));
}
if (context)
return std::unique_ptr<GLContextGLX>(new GLContextGLX(platformDisplay, WTFMove(context), window));
}
}
// Fallback to the config used by the window. We don't probably have the buffers we need in
// this config and that will cause artifacts, but it's better than not rendering anything.
if (hasGLXARBCreateContextExtension(display))
context.reset(createGLXARBContext(display, windowConfig, sharingContext));
else {
// Legacy OpenGL version.
context.reset(glXCreateContext(display, windowVisualInfo.get(), sharingContext, True));
}
if (!context)
return nullptr;
return std::unique_ptr<GLContextGLX>(new GLContextGLX(platformDisplay, WTFMove(context), window));
}
std::unique_ptr<GLContextGLX> GLContextGLX::createPbufferContext(PlatformDisplay& platformDisplay, GLXContext sharingContext)
{
static const int fbConfigAttributes[] = {
GLX_DRAWABLE_TYPE, GLX_PBUFFER_BIT,
GLX_RENDER_TYPE, GLX_RGBA_BIT,
GLX_RED_SIZE, 1,
GLX_GREEN_SIZE, 1,
GLX_BLUE_SIZE, 1,
GLX_ALPHA_SIZE, 1,
GLX_DOUBLEBUFFER, GL_FALSE,
0
};
int returnedElements;
Display* display = downcast<PlatformDisplayX11>(platformDisplay).native();
XUniquePtr<GLXFBConfig> configs(glXChooseFBConfig(display, 0, fbConfigAttributes, &returnedElements));
if (!returnedElements)
return nullptr;
// We will be rendering to a texture, so our pbuffer does not need to be large.
static const int pbufferAttributes[] = { GLX_PBUFFER_WIDTH, 1, GLX_PBUFFER_HEIGHT, 1, 0 };
XUniqueGLXPbuffer pbuffer(glXCreatePbuffer(display, configs.get()[0], pbufferAttributes));
if (!pbuffer)
return nullptr;
XUniqueGLXContext context;
if (hasGLXARBCreateContextExtension(display))
context.reset(createGLXARBContext(display, configs.get()[0], sharingContext));
else {
// Legacy OpenGL version.
context.reset(glXCreateNewContext(display, configs.get()[0], GLX_RGBA_TYPE, sharingContext, GL_TRUE));
}
if (!context)
return nullptr;
return std::unique_ptr<GLContextGLX>(new GLContextGLX(platformDisplay, WTFMove(context), WTFMove(pbuffer)));
}
std::unique_ptr<GLContextGLX> GLContextGLX::createPixmapContext(PlatformDisplay& platformDisplay, GLXContext sharingContext)
{
static int visualAttributes[] = {
GLX_RGBA,
GLX_RED_SIZE, 1,
GLX_GREEN_SIZE, 1,
GLX_BLUE_SIZE, 1,
GLX_ALPHA_SIZE, 1,
0
};
Display* display = downcast<PlatformDisplayX11>(platformDisplay).native();
XUniquePtr<XVisualInfo> visualInfo(glXChooseVisual(display, DefaultScreen(display), visualAttributes));
if (!visualInfo)
return nullptr;
XUniqueGLXContext context(glXCreateContext(display, visualInfo.get(), sharingContext, GL_TRUE));
if (!context)
return nullptr;
XUniquePixmap pixmap(XCreatePixmap(display, DefaultRootWindow(display), 1, 1, visualInfo->depth));
if (!pixmap)
return nullptr;
XUniqueGLXPixmap glxPixmap(glXCreateGLXPixmap(display, visualInfo.get(), pixmap.get()));
if (!glxPixmap)
return nullptr;
return std::unique_ptr<GLContextGLX>(new GLContextGLX(platformDisplay, WTFMove(context), WTFMove(pixmap), WTFMove(glxPixmap)));
}
std::unique_ptr<GLContextGLX> GLContextGLX::createContext(GLNativeWindowType window, PlatformDisplay& platformDisplay)
{
GLXContext glxSharingContext = platformDisplay.sharingGLContext() ? static_cast<GLContextGLX*>(platformDisplay.sharingGLContext())->m_context.get() : nullptr;
auto context = window ? createWindowContext(window, platformDisplay, glxSharingContext) : nullptr;
if (!context)
context = createPbufferContext(platformDisplay, glxSharingContext);
if (!context)
context = createPixmapContext(platformDisplay, glxSharingContext);
return context;
}
std::unique_ptr<GLContextGLX> GLContextGLX::createSharingContext(PlatformDisplay& platformDisplay)
{
auto context = createPbufferContext(platformDisplay);
if (!context)
context = createPixmapContext(platformDisplay);
return context;
}
GLContextGLX::GLContextGLX(PlatformDisplay& display, XUniqueGLXContext&& context, GLNativeWindowType window)
: GLContext(display)
, m_x11Display(downcast<PlatformDisplayX11>(m_display).native())
, m_context(WTFMove(context))
, m_window(static_cast<Window>(window))
{
}
GLContextGLX::GLContextGLX(PlatformDisplay& display, XUniqueGLXContext&& context, XUniqueGLXPbuffer&& pbuffer)
: GLContext(display)
, m_x11Display(downcast<PlatformDisplayX11>(m_display).native())
, m_context(WTFMove(context))
, m_pbuffer(WTFMove(pbuffer))
{
}
GLContextGLX::GLContextGLX(PlatformDisplay& display, XUniqueGLXContext&& context, XUniquePixmap&& pixmap, XUniqueGLXPixmap&& glxPixmap)
: GLContext(display)
, m_x11Display(downcast<PlatformDisplayX11>(m_display).native())
, m_context(WTFMove(context))
, m_pixmap(WTFMove(pixmap))
, m_glxPixmap(WTFMove(glxPixmap))
{
}
GLContextGLX::~GLContextGLX()
{
if (m_cairoDevice)
cairo_device_destroy(m_cairoDevice);
if (m_context) {
// Due to a bug in some nvidia drivers, we need bind the default framebuffer in a context before
// destroying it to avoid a crash. In order to do that, we need to make the context current and,
// after the bind change, we need to set the previous context again.
GLContext* previousActiveContext = GLContext::current();
makeContextCurrent();
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
if (previousActiveContext && previousActiveContext != this) {
// If there was a previous context different from this one, just make it current again.
previousActiveContext->makeContextCurrent();
} else {
// If there was no previous context or this was the previous, set a void context as current.
// We use the GLX function here, and the destructor of GLContext will clean the pointer
// returned by GLContext::current().
glXMakeCurrent(m_x11Display, None, None);
}
}
}
bool GLContextGLX::canRenderToDefaultFramebuffer()
{
return m_window;
}
IntSize GLContextGLX::defaultFrameBufferSize()
{
if (!canRenderToDefaultFramebuffer() || !m_window)
return IntSize();
int x, y;
Window rootWindow;
unsigned int width, height, borderWidth, depth;
if (!XGetGeometry(m_x11Display, m_window, &rootWindow, &x, &y, &width, &height, &borderWidth, &depth))
return IntSize();
return IntSize(width, height);
}
bool GLContextGLX::makeContextCurrent()
{
ASSERT(m_context && (m_window || m_pbuffer || m_glxPixmap));
GLContext::makeContextCurrent();
if (glXGetCurrentContext() == m_context.get())
return true;
if (m_window)
return glXMakeCurrent(m_x11Display, m_window, m_context.get());
if (m_pbuffer)
return glXMakeCurrent(m_x11Display, m_pbuffer.get(), m_context.get());
return ::glXMakeCurrent(m_x11Display, m_glxPixmap.get(), m_context.get());
}
void GLContextGLX::swapBuffers()
{
if (m_window)
glXSwapBuffers(m_x11Display, m_window);
}
void GLContextGLX::waitNative()
{
glXWaitX();
}
void GLContextGLX::swapInterval(int interval)
{
if (!hasSGISwapControlExtension(m_x11Display))
return;
glXSwapIntervalSGI(interval);
}
cairo_device_t* GLContextGLX::cairoDevice()
{
if (m_cairoDevice)
return m_cairoDevice;
#if ENABLE(ACCELERATED_2D_CANVAS) && CAIRO_HAS_GLX_FUNCTIONS
m_cairoDevice = cairo_glx_device_create(m_x11Display, m_context.get());
#endif
return m_cairoDevice;
}
#if ENABLE(GRAPHICS_CONTEXT_3D)
PlatformGraphicsContext3D GLContextGLX::platformContext()
{
return m_context.get();
}
#endif
} // namespace WebCore
#endif // USE(GLX)