1 /*
2
3 Create an OpenGL context without creating an OpenGL Window. for Linux.
4
5 See also
6
7 glxgears.c by Brian Paul from mesa-demos (mesa3d.org)
8 http://cgit.freedesktop.org/mesa/demos/tree/src/xdemos?id=mesa-demos-8.0.1
9 http://www.opengl.org/sdk/docs/man/xhtml/glXIntro.xml
10 http://www.mesa3d.org/brianp/sig97/offscrn.htm
11 http://glprogramming.com/blue/ch07.html
12 OffscreenContext.mm (Mac OSX version)
13
14 */
15
16 /*
17 * Some portions of the code below are:
18 * Copyright (C) 1999-2001 Brian Paul All Rights Reserved.
19 *
20 * Permission is hereby granted, free of charge, to any person obtaining a
21 * copy of this software and associated documentation files (the "Software"),
22 * to deal in the Software without restriction, including without limitation
23 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
24 * and/or sell copies of the Software, and to permit persons to whom the
25 * Software is furnished to do so, subject to the following conditions:
26 *
27 * The above copyright notice and this permission notice shall be included
28 * in all copies or substantial portions of the Software.
29 *
30 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
31 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
32 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
33 * BRIAN PAUL BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
34 * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
35 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
36 */
37
38 #include "OffscreenContext.h"
39 #include "printutils.h"
40 #include "imageutils.h"
41 #include "system-gl.h"
42 #include "fbo.h"
43
44 #include <GL/gl.h>
45 #include <GL/glx.h>
46
47 #include <assert.h>
48 #include <sstream>
49 #include <string>
50
51 #include <sys/utsname.h> // for uname
52
53 struct OffscreenContext
54 {
OffscreenContextOffscreenContext55 OffscreenContext(int width, int height) :
56 openGLContext(nullptr), xdisplay(nullptr), xwindow(0),
57 width(width), height(height),
58 fbo(nullptr) {}
59 GLXContext openGLContext;
60 Display *xdisplay;
61 Window xwindow;
62 int width;
63 int height;
64 fbo_t *fbo;
65 };
66
67 #include "OffscreenContextAll.hpp"
68
get_os_info()69 std::string get_os_info()
70 {
71 struct utsname u;
72
73 if (uname(&u) < 0) {
74 return STR("OS info: unknown, uname() error\n");
75 }
76 else {
77 return STR("OS info: " << u.sysname << " " << u.release << " " << u.version << "\n" <<
78 "Machine: " << u.machine);
79 }
80 return "";
81 }
82
offscreen_context_getinfo(OffscreenContext * ctx)83 std::string offscreen_context_getinfo(OffscreenContext *ctx)
84 {
85 assert(ctx);
86
87 if (!ctx->xdisplay) {
88 return std::string("No GL Context initialized. No information to report\n");
89 }
90
91 int major, minor;
92 glXQueryVersion(ctx->xdisplay, &major, &minor);
93
94 return STR("GL context creator: GLX\n" <<
95 "PNG generator: lodepng\n" <<
96 "GLX version: " << major << "." << minor << "\n" <<
97 get_os_info());
98 }
99
100 static XErrorHandler original_xlib_handler = nullptr;
101 static auto XCreateWindow_failed = false;
XCreateWindow_error(Display * dpy,XErrorEvent * event)102 static int XCreateWindow_error(Display *dpy, XErrorEvent *event)
103 {
104 std::cerr << "XCreateWindow failed: XID: " << event->resourceid
105 << " request: " << static_cast<int>(event->request_code)
106 << " minor: " << static_cast<int>(event->minor_code) << "\n";
107 char description[1024];
108 XGetErrorText( dpy, event->error_code, description, 1023 );
109 std::cerr << " error message: " << description << "\n";
110 XCreateWindow_failed = true;
111 return 0;
112 }
113
114 /*
115 create a dummy X window without showing it. (without 'mapping' it)
116 and save information to the ctx.
117
118 This purposely does not use glxCreateWindow, to avoid crashes,
119 "failed to create drawable" errors, and Mesa "WARNING: Application calling
120 GLX 1.3 function when GLX 1.3 is not supported! This is an application bug!"
121
122 This function will alter ctx.openGLContext and ctx.xwindow if successful
123 */
create_glx_dummy_window(OffscreenContext & ctx)124 bool create_glx_dummy_window(OffscreenContext &ctx)
125 {
126 int attributes[] = {
127 GLX_DRAWABLE_TYPE, GLX_WINDOW_BIT | GLX_PIXMAP_BIT | GLX_PBUFFER_BIT, //support all 3, for OpenCSG
128 GLX_RENDER_TYPE, GLX_RGBA_BIT,
129 GLX_RED_SIZE, 8,
130 GLX_GREEN_SIZE, 8,
131 GLX_BLUE_SIZE, 8,
132 GLX_ALPHA_SIZE, 8,
133 GLX_DEPTH_SIZE, 24, // depth-stencil for OpenCSG
134 GLX_STENCIL_SIZE, 8,
135 GLX_DOUBLEBUFFER, true,
136 None
137 };
138
139 auto dpy = ctx.xdisplay;
140
141 int num_returned = 0;
142 auto fbconfigs = glXChooseFBConfig( dpy, DefaultScreen(dpy), attributes, &num_returned );
143 if (fbconfigs == nullptr) {
144 std::cerr << "glXChooseFBConfig failed\n";
145 return false;
146 }
147
148 auto visinfo = glXGetVisualFromFBConfig( dpy, fbconfigs[0] );
149 if (visinfo == nullptr) {
150 std::cerr << "glXGetVisualFromFBConfig failed\n";
151 XFree(fbconfigs);
152 return false;
153 }
154
155 // can't depend on xWin==nullptr at failure. use a custom Xlib error handler instead.
156 original_xlib_handler = XSetErrorHandler(XCreateWindow_error);
157
158 auto root = DefaultRootWindow(dpy);
159 XSetWindowAttributes xwin_attr;
160 auto width = ctx.width;
161 auto height = ctx.height;
162 xwin_attr.background_pixmap = None;
163 xwin_attr.background_pixel = 0;
164 xwin_attr.border_pixel = 0;
165 xwin_attr.colormap = XCreateColormap( dpy, root, visinfo->visual, AllocNone);
166 xwin_attr.event_mask = StructureNotifyMask | ExposureMask | KeyPressMask;
167 unsigned long int mask = CWBackPixel | CWBorderPixel | CWColormap | CWEventMask;
168
169 auto xWin = XCreateWindow( dpy, root, 0, 0, width, height,
170 0, visinfo->depth, InputOutput,
171 visinfo->visual, mask, &xwin_attr );
172
173 // Window xWin = XCreateSimpleWindow( dpy, DefaultRootWindow(dpy), 0,0,42,42, 0,0,0 );
174
175 XSync(dpy, false);
176 if (XCreateWindow_failed) {
177 XFree(visinfo);
178 XFree(fbconfigs);
179 return false;
180 }
181 XSetErrorHandler(original_xlib_handler);
182
183 // Most programs would call XMapWindow here. But we don't, to keep the window hidden
184 // XMapWindow( dpy, xWin );
185
186 auto context = glXCreateNewContext(dpy, fbconfigs[0], GLX_RGBA_TYPE, nullptr, true);
187 if (context == nullptr) {
188 std::cerr << "glXCreateNewContext failed\n";
189 XDestroyWindow(dpy, xWin);
190 XFree(visinfo);
191 XFree(fbconfigs);
192 return false;
193 }
194
195 //GLXWindow glxWin = glXCreateWindow( dpy, fbconfigs[0], xWin, nullptr );
196
197 if (!glXMakeContextCurrent( dpy, xWin, xWin, context )) {
198 //if (!glXMakeContextCurrent( dpy, glxWin, glxWin, context )) {
199 std::cerr << "glXMakeContextCurrent failed\n";
200 glXDestroyContext(dpy, context);
201 XDestroyWindow(dpy, xWin);
202 XFree(visinfo);
203 XFree(fbconfigs);
204 return false;
205 }
206
207 ctx.openGLContext = context;
208 ctx.xwindow = xWin;
209
210 XFree(visinfo);
211 XFree(fbconfigs);
212
213 return true;
214 }
215
216 bool create_glx_dummy_context(OffscreenContext &ctx);
217
create_offscreen_context(int w,int h)218 OffscreenContext *create_offscreen_context(int w, int h)
219 {
220 auto ctx = new OffscreenContext(w, h);
221
222 // before an FBO can be setup, a GLX context must be created
223 // this call alters ctx->xDisplay and ctx->openGLContext
224 // and ctx->xwindow if successful
225 if (!create_glx_dummy_context(*ctx)) {
226 delete ctx;
227 return nullptr;
228 }
229
230 return create_offscreen_context_common(ctx);
231 }
232
teardown_offscreen_context(OffscreenContext * ctx)233 bool teardown_offscreen_context(OffscreenContext *ctx)
234 {
235 if (ctx) {
236 fbo_unbind(ctx->fbo);
237 fbo_delete(ctx->fbo);
238 XDestroyWindow( ctx->xdisplay, ctx->xwindow );
239 glXDestroyContext( ctx->xdisplay, ctx->openGLContext );
240 XCloseDisplay( ctx->xdisplay );
241 return true;
242 }
243 return false;
244 }
245
save_framebuffer(const OffscreenContext * ctx,std::ostream & output)246 bool save_framebuffer(const OffscreenContext *ctx, std::ostream &output)
247 {
248 glXSwapBuffers(ctx->xdisplay, ctx->xwindow);
249 return save_framebuffer_common(ctx, output);
250 }
251
252 #pragma GCC diagnostic ignored "-Waddress"
create_glx_dummy_context(OffscreenContext & ctx)253 bool create_glx_dummy_context(OffscreenContext &ctx)
254 {
255 // This will alter ctx.openGLContext and ctx.xdisplay and ctx.xwindow if successful
256 int major;
257 int minor;
258 auto result = false;
259
260 ctx.xdisplay = XOpenDisplay(nullptr);
261 if (ctx.xdisplay == nullptr) {
262 std::cerr << "Unable to open a connection to the X server.\n";
263 auto dpyenv = getenv("DISPLAY");
264 std::cerr << "DISPLAY=" << (dpyenv?dpyenv:"") << "\n";
265 return false;
266 }
267
268 // glxQueryVersion is not always reliable. Use it, but then
269 // also check to see if GLX 1.3 functions exist
270
271 glXQueryVersion(ctx.xdisplay, &major, &minor);
272 if (major==1 && minor<=2 && glXGetVisualFromFBConfig==nullptr) {
273 std::cerr << "Error: GLX version 1.3 functions missing. "
274 << "Your GLX version: " << major << "." << minor << std::endl;
275 } else {
276 result = create_glx_dummy_window(ctx);
277 }
278
279 if (!result) XCloseDisplay(ctx.xdisplay);
280 return result;
281 }
282