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