/* Create an OpenGL context without creating an OpenGL Window. for Linux. See also glxgears.c by Brian Paul from mesa-demos (mesa3d.org) http://cgit.freedesktop.org/mesa/demos/tree/src/xdemos?id=mesa-demos-8.0.1 http://www.opengl.org/sdk/docs/man/xhtml/glXIntro.xml http://www.mesa3d.org/brianp/sig97/offscrn.htm http://glprogramming.com/blue/ch07.html OffscreenContext.mm (Mac OSX version) */ /* * Some portions of the code below are: * Copyright (C) 1999-2001 Brian Paul All Rights Reserved. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * BRIAN PAUL BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include "OffscreenContext.h" #include "printutils.h" #include "imageutils.h" #include "system-gl.h" #include "fbo.h" #include #include #include #include #include #include // for uname struct OffscreenContext { OffscreenContext(int width, int height) : openGLContext(nullptr), xdisplay(nullptr), xwindow(0), width(width), height(height), fbo(nullptr) {} GLXContext openGLContext; Display *xdisplay; Window xwindow; int width; int height; fbo_t *fbo; }; #include "OffscreenContextAll.hpp" std::string get_os_info() { struct utsname u; if (uname(&u) < 0) { return STR("OS info: unknown, uname() error\n"); } else { return STR("OS info: " << u.sysname << " " << u.release << " " << u.version << "\n" << "Machine: " << u.machine); } return ""; } std::string offscreen_context_getinfo(OffscreenContext *ctx) { assert(ctx); if (!ctx->xdisplay) { return std::string("No GL Context initialized. No information to report\n"); } int major, minor; glXQueryVersion(ctx->xdisplay, &major, &minor); return STR("GL context creator: GLX\n" << "PNG generator: lodepng\n" << "GLX version: " << major << "." << minor << "\n" << get_os_info()); } static XErrorHandler original_xlib_handler = nullptr; static auto XCreateWindow_failed = false; static int XCreateWindow_error(Display *dpy, XErrorEvent *event) { std::cerr << "XCreateWindow failed: XID: " << event->resourceid << " request: " << static_cast(event->request_code) << " minor: " << static_cast(event->minor_code) << "\n"; char description[1024]; XGetErrorText( dpy, event->error_code, description, 1023 ); std::cerr << " error message: " << description << "\n"; XCreateWindow_failed = true; return 0; } /* create a dummy X window without showing it. (without 'mapping' it) and save information to the ctx. This purposely does not use glxCreateWindow, to avoid crashes, "failed to create drawable" errors, and Mesa "WARNING: Application calling GLX 1.3 function when GLX 1.3 is not supported! This is an application bug!" This function will alter ctx.openGLContext and ctx.xwindow if successful */ bool create_glx_dummy_window(OffscreenContext &ctx) { int attributes[] = { GLX_DRAWABLE_TYPE, GLX_WINDOW_BIT | GLX_PIXMAP_BIT | GLX_PBUFFER_BIT, //support all 3, for OpenCSG GLX_RENDER_TYPE, GLX_RGBA_BIT, GLX_RED_SIZE, 8, GLX_GREEN_SIZE, 8, GLX_BLUE_SIZE, 8, GLX_ALPHA_SIZE, 8, GLX_DEPTH_SIZE, 24, // depth-stencil for OpenCSG GLX_STENCIL_SIZE, 8, GLX_DOUBLEBUFFER, true, None }; auto dpy = ctx.xdisplay; int num_returned = 0; auto fbconfigs = glXChooseFBConfig( dpy, DefaultScreen(dpy), attributes, &num_returned ); if (fbconfigs == nullptr) { std::cerr << "glXChooseFBConfig failed\n"; return false; } auto visinfo = glXGetVisualFromFBConfig( dpy, fbconfigs[0] ); if (visinfo == nullptr) { std::cerr << "glXGetVisualFromFBConfig failed\n"; XFree(fbconfigs); return false; } // can't depend on xWin==nullptr at failure. use a custom Xlib error handler instead. original_xlib_handler = XSetErrorHandler(XCreateWindow_error); auto root = DefaultRootWindow(dpy); XSetWindowAttributes xwin_attr; auto width = ctx.width; auto height = ctx.height; xwin_attr.background_pixmap = None; xwin_attr.background_pixel = 0; xwin_attr.border_pixel = 0; xwin_attr.colormap = XCreateColormap( dpy, root, visinfo->visual, AllocNone); xwin_attr.event_mask = StructureNotifyMask | ExposureMask | KeyPressMask; unsigned long int mask = CWBackPixel | CWBorderPixel | CWColormap | CWEventMask; auto xWin = XCreateWindow( dpy, root, 0, 0, width, height, 0, visinfo->depth, InputOutput, visinfo->visual, mask, &xwin_attr ); // Window xWin = XCreateSimpleWindow( dpy, DefaultRootWindow(dpy), 0,0,42,42, 0,0,0 ); XSync(dpy, false); if (XCreateWindow_failed) { XFree(visinfo); XFree(fbconfigs); return false; } XSetErrorHandler(original_xlib_handler); // Most programs would call XMapWindow here. But we don't, to keep the window hidden // XMapWindow( dpy, xWin ); auto context = glXCreateNewContext(dpy, fbconfigs[0], GLX_RGBA_TYPE, nullptr, true); if (context == nullptr) { std::cerr << "glXCreateNewContext failed\n"; XDestroyWindow(dpy, xWin); XFree(visinfo); XFree(fbconfigs); return false; } //GLXWindow glxWin = glXCreateWindow( dpy, fbconfigs[0], xWin, nullptr ); if (!glXMakeContextCurrent( dpy, xWin, xWin, context )) { //if (!glXMakeContextCurrent( dpy, glxWin, glxWin, context )) { std::cerr << "glXMakeContextCurrent failed\n"; glXDestroyContext(dpy, context); XDestroyWindow(dpy, xWin); XFree(visinfo); XFree(fbconfigs); return false; } ctx.openGLContext = context; ctx.xwindow = xWin; XFree(visinfo); XFree(fbconfigs); return true; } bool create_glx_dummy_context(OffscreenContext &ctx); OffscreenContext *create_offscreen_context(int w, int h) { auto ctx = new OffscreenContext(w, h); // before an FBO can be setup, a GLX context must be created // this call alters ctx->xDisplay and ctx->openGLContext // and ctx->xwindow if successful if (!create_glx_dummy_context(*ctx)) { delete ctx; return nullptr; } return create_offscreen_context_common(ctx); } bool teardown_offscreen_context(OffscreenContext *ctx) { if (ctx) { fbo_unbind(ctx->fbo); fbo_delete(ctx->fbo); XDestroyWindow( ctx->xdisplay, ctx->xwindow ); glXDestroyContext( ctx->xdisplay, ctx->openGLContext ); XCloseDisplay( ctx->xdisplay ); return true; } return false; } bool save_framebuffer(const OffscreenContext *ctx, std::ostream &output) { glXSwapBuffers(ctx->xdisplay, ctx->xwindow); return save_framebuffer_common(ctx, output); } #pragma GCC diagnostic ignored "-Waddress" bool create_glx_dummy_context(OffscreenContext &ctx) { // This will alter ctx.openGLContext and ctx.xdisplay and ctx.xwindow if successful int major; int minor; auto result = false; ctx.xdisplay = XOpenDisplay(nullptr); if (ctx.xdisplay == nullptr) { std::cerr << "Unable to open a connection to the X server.\n"; auto dpyenv = getenv("DISPLAY"); std::cerr << "DISPLAY=" << (dpyenv?dpyenv:"") << "\n"; return false; } // glxQueryVersion is not always reliable. Use it, but then // also check to see if GLX 1.3 functions exist glXQueryVersion(ctx.xdisplay, &major, &minor); if (major==1 && minor<=2 && glXGetVisualFromFBConfig==nullptr) { std::cerr << "Error: GLX version 1.3 functions missing. " << "Your GLX version: " << major << "." << minor << std::endl; } else { result = create_glx_dummy_window(ctx); } if (!result) XCloseDisplay(ctx.xdisplay); return result; }