| /* |
| * Copyright 2011 Google Inc. |
| * |
| * Use of this source code is governed by a BSD-style license that can be |
| * found in the LICENSE file. |
| */ |
| |
| #include "gl/GLTestContext.h" |
| #include "SkOnce.h" |
| |
| #include <X11/Xlib.h> |
| #include <GL/glx.h> |
| #include <GL/glu.h> |
| |
| #include <vector> |
| #include <utility> |
| |
| namespace { |
| |
| /* Note: Skia requires glx 1.3 or newer */ |
| |
| /* This struct is taken from a mesa demo. Please update as required */ |
| static const std::vector<std::pair<int, int>> gl_versions = { |
| {1, 0}, |
| {1, 1}, |
| {1, 2}, |
| {1, 3}, |
| {1, 4}, |
| {1, 5}, |
| {2, 0}, |
| {2, 1}, |
| {3, 0}, |
| {3, 1}, |
| {3, 2}, |
| {3, 3}, |
| {4, 0}, |
| {4, 1}, |
| {4, 2}, |
| {4, 3}, |
| {4, 4}, |
| }; |
| |
| static const std::vector<std::pair<int, int>> gles_versions = { |
| {2, 0}, |
| {3, 0}, |
| }; |
| |
| static bool ctxErrorOccurred = false; |
| static int ctxErrorHandler(Display *dpy, XErrorEvent *ev) { |
| ctxErrorOccurred = true; |
| return 0; |
| } |
| |
| class GLXGLTestContext : public sk_gpu_test::GLTestContext { |
| public: |
| GLXGLTestContext(GrGLStandard forcedGpuAPI, GLXGLTestContext* shareList); |
| ~GLXGLTestContext() override; |
| |
| private: |
| void destroyGLContext(); |
| static GLXContext CreateBestContext(bool isES, Display* display, GLXFBConfig bestFbc, |
| GLXContext glxSharedContext); |
| |
| void onPlatformMakeCurrent() const override; |
| void onPlatformSwapBuffers() const override; |
| GrGLFuncPtr onPlatformGetProcAddress(const char*) const override; |
| |
| GLXContext fContext; |
| Display* fDisplay; |
| Pixmap fPixmap; |
| GLXPixmap fGlxPixmap; |
| }; |
| |
| static Display* get_display() { |
| class AutoDisplay { |
| public: |
| AutoDisplay() { fDisplay = XOpenDisplay(nullptr); } |
| ~AutoDisplay() { |
| if (fDisplay) { |
| XCloseDisplay(fDisplay); |
| } |
| } |
| Display* display() const { return fDisplay; } |
| private: |
| Display* fDisplay; |
| }; |
| static std::unique_ptr<AutoDisplay> ad; |
| static SkOnce once; |
| once([] { ad.reset(new AutoDisplay{}); }); |
| return ad->display(); |
| } |
| |
| GLXGLTestContext::GLXGLTestContext(GrGLStandard forcedGpuAPI, GLXGLTestContext* shareContext) |
| : fContext(nullptr) |
| , fDisplay(nullptr) |
| , fPixmap(0) |
| , fGlxPixmap(0) { |
| fDisplay = get_display(); |
| |
| GLXContext glxShareContext = shareContext ? shareContext->fContext : nullptr; |
| |
| if (!fDisplay) { |
| SkDebugf("Failed to open X display.\n"); |
| this->destroyGLContext(); |
| return; |
| } |
| |
| // Get a matching FB config |
| static int visual_attribs[] = { |
| GLX_X_RENDERABLE , True, |
| GLX_DRAWABLE_TYPE , GLX_PIXMAP_BIT, |
| None |
| }; |
| |
| int glx_major, glx_minor; |
| |
| // FBConfigs were added in GLX version 1.3. |
| if (!glXQueryVersion(fDisplay, &glx_major, &glx_minor) || |
| ((glx_major == 1) && (glx_minor < 3)) || (glx_major < 1)) { |
| SkDebugf("GLX version 1.3 or higher required.\n"); |
| this->destroyGLContext(); |
| return; |
| } |
| |
| //SkDebugf("Getting matching framebuffer configs.\n"); |
| int fbcount; |
| GLXFBConfig *fbc = glXChooseFBConfig(fDisplay, DefaultScreen(fDisplay), |
| visual_attribs, &fbcount); |
| if (!fbc) { |
| SkDebugf("Failed to retrieve a framebuffer config.\n"); |
| this->destroyGLContext(); |
| return; |
| } |
| //SkDebugf("Found %d matching FB configs.\n", fbcount); |
| |
| // Pick the FB config/visual with the most samples per pixel |
| //SkDebugf("Getting XVisualInfos.\n"); |
| int best_fbc = -1, best_num_samp = -1; |
| |
| int i; |
| for (i = 0; i < fbcount; ++i) { |
| XVisualInfo *vi = glXGetVisualFromFBConfig(fDisplay, fbc[i]); |
| if (vi) { |
| int samp_buf, samples; |
| glXGetFBConfigAttrib(fDisplay, fbc[i], GLX_SAMPLE_BUFFERS, &samp_buf); |
| glXGetFBConfigAttrib(fDisplay, fbc[i], GLX_SAMPLES, &samples); |
| |
| //SkDebugf(" Matching fbconfig %d, visual ID 0x%2x: SAMPLE_BUFFERS = %d," |
| // " SAMPLES = %d\n", |
| // i, (unsigned int)vi->visualid, samp_buf, samples); |
| |
| if (best_fbc < 0 || (samp_buf && samples > best_num_samp)) { |
| best_fbc = i; |
| best_num_samp = samples; |
| } |
| } |
| XFree(vi); |
| } |
| |
| GLXFBConfig bestFbc = fbc[best_fbc]; |
| |
| // Be sure to free the FBConfig list allocated by glXChooseFBConfig() |
| XFree(fbc); |
| |
| // Get a visual |
| XVisualInfo *vi = glXGetVisualFromFBConfig(fDisplay, bestFbc); |
| //SkDebugf("Chosen visual ID = 0x%x\n", (unsigned int)vi->visualid); |
| |
| fPixmap = XCreatePixmap(fDisplay, RootWindow(fDisplay, vi->screen), 10, 10, vi->depth); |
| |
| if (!fPixmap) { |
| SkDebugf("Failed to create pixmap.\n"); |
| this->destroyGLContext(); |
| return; |
| } |
| |
| fGlxPixmap = glXCreateGLXPixmap(fDisplay, vi, fPixmap); |
| |
| // Done with the visual info data |
| XFree(vi); |
| |
| // Get the default screen's GLX extension list |
| const char *glxExts = glXQueryExtensionsString( |
| fDisplay, DefaultScreen(fDisplay) |
| ); |
| // Check for the GLX_ARB_create_context extension string and the function. |
| // If either is not present, use GLX 1.3 context creation method. |
| if (!gluCheckExtension(reinterpret_cast<const GLubyte*>("GLX_ARB_create_context"), |
| reinterpret_cast<const GLubyte*>(glxExts))) { |
| if (kGLES_GrGLStandard != forcedGpuAPI) { |
| fContext = glXCreateNewContext(fDisplay, bestFbc, GLX_RGBA_TYPE, 0, True); |
| } |
| } else { |
| if (kGLES_GrGLStandard == forcedGpuAPI) { |
| if (gluCheckExtension( |
| reinterpret_cast<const GLubyte*>("GLX_EXT_create_context_es2_profile"), |
| reinterpret_cast<const GLubyte*>(glxExts))) { |
| fContext = CreateBestContext(true, fDisplay, bestFbc, glxShareContext); |
| } |
| } else { |
| fContext = CreateBestContext(false, fDisplay, bestFbc, glxShareContext); |
| } |
| } |
| if (!fContext) { |
| SkDebugf("Failed to create an OpenGL context.\n"); |
| this->destroyGLContext(); |
| return; |
| } |
| |
| // Verify that context is a direct context |
| if (!glXIsDirect(fDisplay, fContext)) { |
| //SkDebugf("Indirect GLX rendering context obtained.\n"); |
| } else { |
| //SkDebugf("Direct GLX rendering context obtained.\n"); |
| } |
| |
| //SkDebugf("Making context current.\n"); |
| if (!glXMakeCurrent(fDisplay, fGlxPixmap, fContext)) { |
| SkDebugf("Could not set the context.\n"); |
| this->destroyGLContext(); |
| return; |
| } |
| |
| sk_sp<const GrGLInterface> gl(GrGLCreateNativeInterface()); |
| if (nullptr == gl.get()) { |
| SkDebugf("Failed to create gl interface"); |
| this->destroyGLContext(); |
| return; |
| } |
| |
| if (!gl->validate()) { |
| SkDebugf("Failed to validate gl interface"); |
| this->destroyGLContext(); |
| return; |
| } |
| |
| this->init(gl.release()); |
| } |
| |
| |
| GLXGLTestContext::~GLXGLTestContext() { |
| this->teardown(); |
| this->destroyGLContext(); |
| } |
| |
| void GLXGLTestContext::destroyGLContext() { |
| if (fDisplay) { |
| glXMakeCurrent(fDisplay, 0, 0); |
| |
| if (fContext) { |
| glXDestroyContext(fDisplay, fContext); |
| fContext = nullptr; |
| } |
| |
| if (fGlxPixmap) { |
| glXDestroyGLXPixmap(fDisplay, fGlxPixmap); |
| fGlxPixmap = 0; |
| } |
| |
| if (fPixmap) { |
| XFreePixmap(fDisplay, fPixmap); |
| fPixmap = 0; |
| } |
| |
| fDisplay = nullptr; |
| } |
| } |
| |
| /* Create a context with the highest possible version. |
| * |
| * Disable Xlib errors for the duration of this function (by default they abort |
| * the program) and try to get a context starting from the highest version |
| * number - there is no way to just directly ask what the highest supported |
| * version is. |
| * |
| * Returns the correct context or NULL on failure. |
| */ |
| GLXContext GLXGLTestContext::CreateBestContext(bool isES, Display* display, GLXFBConfig bestFbc, |
| GLXContext glxShareContext) { |
| auto glXCreateContextAttribsARB = (PFNGLXCREATECONTEXTATTRIBSARBPROC) |
| glXGetProcAddressARB((GrGLubyte*)"glXCreateContextAttribsARB"); |
| if (!glXCreateContextAttribsARB) { |
| SkDebugf("Failed to get address of glXCreateContextAttribsARB"); |
| return nullptr; |
| } |
| GLXContext context = nullptr; |
| // Install Xlib error handler that will set ctxErrorOccurred. |
| // WARNING: It is global for all threads. |
| ctxErrorOccurred = false; |
| int (*oldHandler)(Display*, XErrorEvent*) = XSetErrorHandler(&ctxErrorHandler); |
| |
| auto versions = isES ? gles_versions : gl_versions; |
| // Well, unfortunately GLX will not just give us the highest context so |
| // instead we have to do this nastiness |
| for (int i = versions.size() - 1; i >= 0 ; i--) { |
| // WARNING: Don't try to optimize this and make this array static. The |
| // glXCreateContextAttribsARB call writes to it upon failure and the |
| // next call would fail too. |
| std::vector<int> flags = { |
| GLX_CONTEXT_MAJOR_VERSION_ARB, versions[i].first, |
| GLX_CONTEXT_MINOR_VERSION_ARB, versions[i].second, |
| }; |
| if (isES) { |
| flags.push_back(GLX_CONTEXT_PROFILE_MASK_ARB); |
| // the ES2 flag should work even for higher versions |
| flags.push_back(GLX_CONTEXT_ES2_PROFILE_BIT_EXT); |
| } else if (versions[i].first > 2) { |
| flags.push_back(GLX_CONTEXT_PROFILE_MASK_ARB); |
| // TODO When Nvidia implements NVPR on Core profiles, we should start |
| // requesting core here - currently Nv Path rendering on Nvidia |
| // requires a compatibility profile. |
| flags.push_back(GLX_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB); |
| } |
| flags.push_back(0); |
| context = glXCreateContextAttribsARB(display, bestFbc, glxShareContext, true, |
| &flags[0]); |
| // Sync to ensure any errors generated are processed. |
| XSync(display, False); |
| |
| if (!ctxErrorOccurred && context) { |
| break; |
| } |
| // try again |
| ctxErrorOccurred = false; |
| } |
| // Restore the original error handler. |
| XSetErrorHandler(oldHandler); |
| return context; |
| } |
| |
| void GLXGLTestContext::onPlatformMakeCurrent() const { |
| if (!glXMakeCurrent(fDisplay, fGlxPixmap, fContext)) { |
| SkDebugf("Could not set the context.\n"); |
| } |
| } |
| |
| void GLXGLTestContext::onPlatformSwapBuffers() const { |
| glXSwapBuffers(fDisplay, fGlxPixmap); |
| } |
| |
| GrGLFuncPtr GLXGLTestContext::onPlatformGetProcAddress(const char* procName) const { |
| return glXGetProcAddress(reinterpret_cast<const GLubyte*>(procName)); |
| } |
| |
| } // anonymous namespace |
| |
| namespace sk_gpu_test { |
| GLTestContext *CreatePlatformGLTestContext(GrGLStandard forcedGpuAPI, |
| GLTestContext *shareContext) { |
| GLXGLTestContext *glxShareContext = reinterpret_cast<GLXGLTestContext *>(shareContext); |
| GLXGLTestContext *ctx = new GLXGLTestContext(forcedGpuAPI, glxShareContext); |
| if (!ctx->isValid()) { |
| delete ctx; |
| return nullptr; |
| } |
| return ctx; |
| } |
| } // namespace sk_gpu_test |