1/*
2 * Copyright (C) 2011 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
16 *  Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17 *  Boston, MA 02110-1301 USA
18 */
19
20#include "config.h"
21#include "GraphicsContext3DInternal.h"
22
23#if ENABLE(WEBGL)
24
25#include "GraphicsContext3D.h"
26#include "OpenGLShims.h"
27#include <GL/glx.h>
28#include <dlfcn.h>
29
30// We do not want to call glXMakeContextCurrent using different Display pointers,
31// because it might lead to crashes in some drivers (fglrx). We use a shared display
32// pointer here.
33static Display* gSharedDisplay = 0;
34static Display* sharedDisplay()
35{
36    if (!gSharedDisplay)
37        gSharedDisplay = XOpenDisplay(0);
38    return gSharedDisplay;
39}
40
41namespace WebCore {
42
43// Because of driver bugs, exiting the program when there are active pbuffers
44// can crash the X server (this has been observed with the official Nvidia drivers).
45// We need to ensure that we clean everything up on exit. There are several reasons
46// that GraphicsContext3Ds will still be alive at exit, including user error (memory
47// leaks) and the page cache. In any case, we don't want the X server to crash.
48static bool cleaningUpAtExit = false;
49static Vector<GraphicsContext3D*>& activeGraphicsContexts()
50{
51    DEFINE_STATIC_LOCAL(Vector<GraphicsContext3D*>, contexts, ());
52    return contexts;
53}
54
55void GraphicsContext3DInternal::addActiveGraphicsContext(GraphicsContext3D* context)
56{
57    static bool addedAtExitHandler = false;
58    if (!addedAtExitHandler) {
59        atexit(&GraphicsContext3DInternal::cleanupActiveContextsAtExit);
60        addedAtExitHandler = true;
61    }
62    activeGraphicsContexts().append(context);
63}
64
65void GraphicsContext3DInternal::removeActiveGraphicsContext(GraphicsContext3D* context)
66{
67    if (cleaningUpAtExit)
68        return;
69
70    Vector<GraphicsContext3D*>& contexts = activeGraphicsContexts();
71    size_t location = contexts.find(context);
72    if (location != WTF::notFound)
73        contexts.remove(location);
74}
75
76void GraphicsContext3DInternal::cleanupActiveContextsAtExit()
77{
78    cleaningUpAtExit = true;
79
80    Vector<GraphicsContext3D*>& contexts = activeGraphicsContexts();
81    for (size_t i = 0; i < contexts.size(); i++)
82        contexts[i]->~GraphicsContext3D();
83
84    if (!gSharedDisplay)
85        return;
86    XCloseDisplay(gSharedDisplay);
87    gSharedDisplay = 0;
88}
89
90GraphicsContext3DInternal* GraphicsContext3DInternal::create()
91{
92    if (!sharedDisplay())
93        return 0;
94
95    static bool initialized = false;
96    static bool success = true;
97    if (!initialized) {
98        success = initializeOpenGLShims();
99        initialized = true;
100    }
101    if (!success)
102        return 0;
103
104    GraphicsContext3DInternal* internal = createPbufferContext();
105    if (!internal)
106        internal = createPixmapContext();
107    if (!internal)
108        return 0;
109
110    // The GraphicsContext3D constructor requires that this context is the current OpenGL context.
111    internal->makeContextCurrent();
112    return internal;
113}
114
115GraphicsContext3DInternal* GraphicsContext3DInternal::createPbufferContext()
116{
117    int fbConfigAttributes[] = {
118        GLX_DRAWABLE_TYPE, GLX_PBUFFER_BIT,
119        GLX_RENDER_TYPE, GLX_RGBA_BIT,
120        GLX_RED_SIZE, 1,
121        GLX_GREEN_SIZE, 1,
122        GLX_BLUE_SIZE, 1,
123        GLX_ALPHA_SIZE, 1,
124        GLX_DEPTH_SIZE, 1,
125        GLX_STENCIL_SIZE, 1,
126        GLX_SAMPLE_BUFFERS, 1,
127        GLX_DOUBLEBUFFER, GL_FALSE,
128        GLX_SAMPLES, 4,
129        0
130    };
131    int returnedElements;
132    GLXFBConfig* configs = glXChooseFBConfig(sharedDisplay(), 0, fbConfigAttributes, &returnedElements);
133    if (!configs) {
134        fbConfigAttributes[20] = 0; // Attempt without anti-aliasing.
135        configs = glXChooseFBConfig(sharedDisplay(), 0, fbConfigAttributes, &returnedElements);
136    }
137    if (!returnedElements) {
138        XFree(configs);
139        return 0;
140    }
141
142    // We will be rendering to a texture, so our pbuffer does not need to be large.
143    static const int pbufferAttributes[] = { GLX_PBUFFER_WIDTH, 1, GLX_PBUFFER_HEIGHT, 1, 0 };
144    GLXPbuffer pbuffer = glXCreatePbuffer(sharedDisplay(), configs[0], pbufferAttributes);
145    if (!pbuffer) {
146        XFree(configs);
147        return 0;
148    }
149
150    GLXContext context = glXCreateNewContext(sharedDisplay(), configs[0], GLX_RGBA_TYPE, 0, GL_TRUE);
151    XFree(configs);
152    if (!context)
153        return 0;
154    return new GraphicsContext3DInternal(context, pbuffer);
155}
156
157GraphicsContext3DInternal* GraphicsContext3DInternal::createPixmapContext()
158{
159    static int visualAttributes[] = {
160        GLX_RGBA,
161        GLX_RED_SIZE, 1,
162        GLX_GREEN_SIZE, 1,
163        GLX_BLUE_SIZE, 1,
164        GLX_ALPHA_SIZE, 1,
165        GLX_DOUBLEBUFFER,
166        0
167    };
168
169    XVisualInfo* visualInfo = glXChooseVisual(sharedDisplay(), DefaultScreen(sharedDisplay()), visualAttributes);
170    if (!visualInfo)
171        return 0;
172
173    GLXContext context = glXCreateContext(sharedDisplay(), visualInfo, 0, GL_TRUE);
174    if (!context) {
175        XFree(visualInfo);
176        return 0;
177    }
178
179    Pixmap pixmap = XCreatePixmap(sharedDisplay(), DefaultRootWindow(sharedDisplay()), 1, 1, visualInfo->depth);
180    if (!pixmap) {
181        XFree(visualInfo);
182        return 0;
183    }
184
185    GLXPixmap glxPixmap = glXCreateGLXPixmap(sharedDisplay(), visualInfo, pixmap);
186    if (!glxPixmap) {
187        XFreePixmap(sharedDisplay(), pixmap);
188        XFree(visualInfo);
189        return 0;
190    }
191
192    return new GraphicsContext3DInternal(context, pixmap, glxPixmap);
193}
194
195GraphicsContext3DInternal::GraphicsContext3DInternal(GLXContext context, GLXPbuffer pbuffer)
196    : m_context(context)
197    , m_pbuffer(pbuffer)
198    , m_pixmap(0)
199    , m_glxPixmap(0)
200{
201}
202
203GraphicsContext3DInternal::GraphicsContext3DInternal(GLXContext context, Pixmap pixmap, GLXPixmap glxPixmap)
204    : m_context(context)
205    , m_pbuffer(0)
206    , m_pixmap(pixmap)
207    , m_glxPixmap(glxPixmap)
208{
209}
210
211GraphicsContext3DInternal::~GraphicsContext3DInternal()
212{
213    if (m_context) {
214        // This may be necessary to prevent crashes with NVidia's closed source drivers. Originally
215        // from Mozilla's 3D canvas implementation at: http://bitbucket.org/ilmari/canvas3d/
216        ::glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
217
218        ::glXMakeContextCurrent(sharedDisplay(), 0, 0, 0);
219        ::glXDestroyContext(sharedDisplay(), m_context);
220        m_context = 0;
221    }
222
223    if (m_pbuffer) {
224        ::glXDestroyPbuffer(sharedDisplay(), m_pbuffer);
225        m_pbuffer = 0;
226    }
227    if (m_glxPixmap) {
228        glXDestroyGLXPixmap(sharedDisplay(), m_glxPixmap);
229        m_glxPixmap = 0;
230    }
231    if (m_pixmap) {
232        XFreePixmap(sharedDisplay(), m_pixmap);
233        m_pixmap = 0;
234    }
235}
236
237void GraphicsContext3DInternal::makeContextCurrent()
238{
239    if (::glXGetCurrentContext() == m_context)
240        return;
241    if (!m_context)
242        return;
243    if (m_pbuffer) {
244        ::glXMakeCurrent(sharedDisplay(), m_pbuffer, m_context);
245        return;
246    }
247
248    ASSERT(m_glxPixmap);
249    ::glXMakeCurrent(sharedDisplay(), m_glxPixmap, m_context);
250}
251
252} // namespace WebCore
253
254#endif // ENABLE_WEBGL
255