1 module gfx.window.xcb.context;
2 
3 version(linux):
4 
5 import gfx.bindings.core : SharedLib;
6 import gfx.gl3.context : GlAttribs, GlContext, glVersions;
7 import X11.Xlib : XDisplay = Display, XErrorEvent;
8 
9 /// GlX backed OpenGL context
10 class XcbGlContext : GlContext
11 {
12     import gfx.bindings.core : SharedSym;
13     import gfx.bindings.opengl.gl : Gl;
14     import gfx.bindings.opengl.glx : Glx, GLXContext, GLXFBConfig;
15     import gfx.core.rc : atomicRcCode, Disposable;
16 
17     mixin(atomicRcCode);
18 
19     private XDisplay* _dpy;
20     private int _mainScreenNum;
21     private GlAttribs _attribs;
22     private Glx _glx;
23     private Gl _gl;
24     private string[] _glxAvailExts;
25     private string[] _glAvailExts;
26     private DummyWindow[size_t] dummies;
27     private size_t hiddenDummy;
28     private GLXContext _ctx;
29     private bool ARB_create_context;
30     private bool MESA_query_renderer;
31     private bool MESA_swap_control;
32     private bool EXT_swap_control;
33 
34     /// Contruct an OpenGL context for the given display and screen.
35     /// It internally creates a dummy window
36     this (XDisplay* dpy, in int mainScreenNum, in GlAttribs attribs)
37     {
38         import gfx.bindings.core : openSharedLib, loadSharedSym, SharedLib;
39         import gfx.bindings.opengl : splitExtString;
40         import gfx.bindings.opengl.gl : GL_EXTENSIONS;
41         import gfx.bindings.opengl.glx : PFN_glXGetProcAddressARB;
42         import std.algorithm : canFind;
43         import std.exception : enforce;
44         import std.experimental.logger : trace, tracef;
45         import X11.Xlib : XSetErrorHandler, XSync;
46 
47         _dpy = dpy;
48         _mainScreenNum = mainScreenNum;
49         _attribs = attribs;
50 
51         auto lib = loadGlLib();
52         auto getProcAddress = cast(PFN_glXGetProcAddressARB)enforce(loadSharedSym(lib, "glXGetProcAddressARB"));
53         SharedSym loadSymbol(in string symbol) {
54             import std.string : toStringz;
55             return cast(SharedSym)getProcAddress(cast(const(ubyte)*)toStringz(symbol));
56         }
57 
58         _glx = new Glx(&loadSymbol);
59 
60         const glxExts = splitExtString(_glx.QueryExtensionsString(_dpy, _mainScreenNum));
61         ARB_create_context = glxExts.canFind("GLX_ARB_create_context");
62         MESA_query_renderer = glxExts.canFind("GLX_MESA_query_renderer");
63         MESA_swap_control = glxExts.canFind("GLX_MESA_swap_control");
64         EXT_swap_control = glxExts.canFind("GLX_EXT_swap_control");
65 
66         enforce( ARB_create_context && ( MESA_swap_control || EXT_swap_control ));
67 
68         auto fbc = getGlxFBConfig(attribs);
69         hiddenDummy = XcbGlContext.createDummy();
70         GlAttribs attrs = attribs;
71 
72         auto oldHandler = XSetErrorHandler(&createCtxErrorHandler);
73 
74         foreach (const glVer; glVersions) {
75             attrs.majorVersion = glVer / 10;
76             attrs.minorVersion = glVer % 10;
77             if (attrs.decimalVersion < attribs.decimalVersion) break;
78 
79             const ctxAttribs = getCtxAttribs(attrs);
80             tracef("attempting to create OpenGL %s.%s context", attrs.majorVersion, attrs.minorVersion);
81 
82             createContextErrorFlag = false;
83             _ctx = _glx.CreateContextAttribsARB(_dpy, fbc, null, 1, &ctxAttribs[0]);
84 
85             if (_ctx && !createContextErrorFlag) break;
86         }
87 
88         XSetErrorHandler(oldHandler);
89 
90         enforce(_ctx);
91         XSync(_dpy, 0);
92         _attribs = attrs;
93 
94         tracef("created OpenGL %s.%s context", attrs.majorVersion, attrs.minorVersion);
95 
96         XcbGlContext.makeCurrent(hiddenDummy);
97         _gl = new Gl(&loadSymbol);
98 
99         trace("done loading GL/GLX");
100     }
101 
102     override void dispose() {
103         import gfx.bindings.core : closeSharedLib;
104         import gfx.core.rc : disposeArray;
105         import std.experimental.logger : trace;
106 
107         disposeArray(dummies);
108 
109         _glx.DestroyContext(_dpy, _ctx);
110         trace("destroyed GL/GLX context");
111     }
112 
113 
114     override @property Gl gl() {
115         return _gl;
116     }
117 
118     override @property GlAttribs attribs() {
119         return _attribs;
120     }
121 
122     override bool makeCurrent(size_t nativeHandle)
123     {
124         import gfx.bindings.opengl.glx : GLXDrawable;
125         return _glx.MakeCurrent(_dpy, cast(GLXDrawable)nativeHandle, _ctx) != 0;
126     }
127 
128     override void doneCurrent()
129     {
130         _glx.MakeCurrent(_dpy, 0, null);
131     }
132 
133     override @property bool current() const
134     {
135         return _glx.GetCurrentContext() is _ctx;
136     }
137 
138     override @property int swapInterval()
139     {
140         import gfx.bindings.opengl.glx : GLXDrawable;
141         if (MESA_swap_control) {
142             return _glx.GetSwapIntervalMESA();
143         }
144         else if (EXT_swap_control) {
145             GLXDrawable drawable = _glx.GetCurrentDrawable();
146             uint swap;
147 
148             if (drawable) {
149                 import gfx.bindings.opengl.glx : GLX_SWAP_INTERVAL_EXT;
150                 _glx.QueryDrawable(_dpy, drawable, GLX_SWAP_INTERVAL_EXT, &swap);
151                 return swap;
152             }
153             else {
154                 import std.experimental.logger : warningf;
155                 warningf("could not get glx drawable to get swap interval");
156                 return -1;
157             }
158 
159         }
160         return -1;
161     }
162 
163     override @property void swapInterval(int interval)
164     {
165         import gfx.bindings.opengl.glx : GLXDrawable;
166 
167         if (MESA_swap_control) {
168             _glx.SwapIntervalMESA(interval);
169         }
170         else if (EXT_swap_control) {
171             GLXDrawable drawable = _glx.GetCurrentDrawable();
172 
173             if (drawable) {
174                 _glx.SwapIntervalEXT(_dpy, drawable, interval);
175             }
176             else {
177                 import std.experimental.logger : warningf;
178                 warningf("could not get glx drawable to set swap interval");
179             }
180         }
181     }
182 
183     override void swapBuffers(size_t nativeHandle)
184     {
185         import gfx.bindings.opengl.glx : GLXDrawable;
186         _glx.SwapBuffers(_dpy, cast(GLXDrawable)nativeHandle);
187     }
188 
189     private GLXFBConfig getGlxFBConfig(in GlAttribs attribs)
190     {
191         import X11.Xlib : XFree;
192 
193         const glxAttribs = getGlxAttribs(attribs);
194 
195         int numConfigs;
196         auto fbConfigs = _glx.ChooseFBConfig(_dpy, _mainScreenNum, &glxAttribs[0], &numConfigs);
197 
198         if (!fbConfigs || !numConfigs)
199         {
200             import std.experimental.logger : critical;
201             critical("GFX-GLX: could not get fb config");
202             return null;
203         }
204         scope (exit) XFree(fbConfigs);
205 
206         return fbConfigs[0];
207     }
208 
209     override size_t createDummy() {
210         if (hiddenDummy) {
211             const d = hiddenDummy;
212             hiddenDummy = 0;
213             return d;
214         }
215         auto dummy = new DummyWindow(_dpy, _glx, getGlxFBConfig(_attribs));
216         size_t hdl = dummy.win;
217         dummies[hdl] = dummy;
218         return hdl;
219     }
220 
221     override void releaseDummy(size_t dummy) {
222         auto d = dummy in dummies;
223         if (d) {
224             auto win = *d;
225             win.dispose();
226             dummies.remove(dummy);
227         }
228     }
229 
230     private static class DummyWindow : Disposable
231     {
232         import X11.Xlib : XWindow = Window, XColormap = Colormap;
233         import X11.Xlib;
234 
235         XWindow win;
236         XColormap cmap;
237         XDisplay* dpy;
238 
239         this (XDisplay* dpy, Glx glx, GLXFBConfig fbc)
240         {
241             this.dpy = dpy;
242             auto vi = glx.GetVisualFromFBConfig( dpy, fbc );
243             scope(exit) {
244                 XFree(vi);
245             }
246 
247             cmap = XCreateColormap(dpy, XRootWindow(dpy, XDefaultScreen(dpy)),
248                                                 vi.visual, AllocNone );
249 
250             XSetWindowAttributes swa;
251             swa.colormap = cmap;
252             swa.background_pixmap = None ;
253             swa.border_pixel      = 0;
254             swa.event_mask        = StructureNotifyMask;
255 
256             win = XCreateWindow(dpy, XRootWindow(dpy, vi.screen),
257                     0, 0, 100, 100, 0, vi.depth, InputOutput, vi.visual,
258                     CWBorderPixel|CWColormap|CWEventMask, &swa);
259         }
260 
261         override void dispose()
262         {
263             XDestroyWindow(dpy, win);
264             XFreeColormap(dpy, cmap);
265         }
266     }
267 }
268 
269 
270 private SharedLib loadGlLib()
271 {
272     import gfx.bindings.core : openSharedLib;
273 
274     immutable glLibNames = ["libGL.so.1", "libGL.so"];
275 
276     foreach (ln; glLibNames) {
277         auto lib = openSharedLib(ln);
278         if (lib) {
279             import std.experimental.logger : tracef;
280             tracef("opening shared library %s", ln);
281             return lib;
282         }
283     }
284 
285     import std.conv : to;
286     throw new Exception("could not load any of these libraries: " ~ glLibNames.to!string);
287 }
288 
289 private bool createContextErrorFlag;
290 
291 extern(C) private int createCtxErrorHandler(XDisplay *dpy, XErrorEvent *error)
292 {
293    createContextErrorFlag = true;
294    return 0;
295 }
296 
297 private int[] getGlxAttribs(in GlAttribs attribs) pure
298 {
299     import gfx.bindings.opengl.glx;
300     import gfx.graal.format : formatDesc, redBits, greenBits, blueBits,
301                               alphaBits, depthBits, stencilBits;
302 
303     int[] glxAttribs = [
304         GLX_X_RENDERABLE,   1,
305         GLX_DRAWABLE_TYPE,  GLX_WINDOW_BIT,
306         GLX_RENDER_TYPE,    GLX_RGBA_BIT,
307         GLX_X_VISUAL_TYPE,  GLX_TRUE_COLOR
308     ];
309 
310     const colorDesc = formatDesc(attribs.colorFormat);
311     const depthStencilDesc = formatDesc(attribs.depthStencilFormat);
312 
313     const r = redBits(colorDesc.surfaceType);
314     const g = greenBits(colorDesc.surfaceType);
315     const b = blueBits(colorDesc.surfaceType);
316     const a = alphaBits(colorDesc.surfaceType);
317     const d = depthBits(depthStencilDesc.surfaceType);
318     const s = stencilBits(depthStencilDesc.surfaceType);
319 
320     if (r) glxAttribs ~= [GLX_RED_SIZE, r];
321     if (g) glxAttribs ~= [GLX_GREEN_SIZE, g];
322     if (b) glxAttribs ~= [GLX_BLUE_SIZE, b];
323     if (a) glxAttribs ~= [GLX_ALPHA_SIZE, a];
324     if (d) glxAttribs ~= [GLX_DEPTH_SIZE, d];
325     if (s) glxAttribs ~= [GLX_STENCIL_SIZE, s];
326 
327     if (attribs.doublebuffer) glxAttribs ~= [GLX_DOUBLEBUFFER, 1];
328 
329     if (attribs.samples > 1)
330         glxAttribs ~= [GLX_SAMPLE_BUFFERS, 1, GLX_SAMPLES, attribs.samples];
331 
332     return glxAttribs ~ 0;
333 }
334 
335 private int[] getCtxAttribs(in GlAttribs attribs) pure
336 {
337     import gfx.bindings.opengl.glx;
338     import gfx.gl3.context : GlProfile;
339 
340     int[] ctxAttribs = [
341         GLX_CONTEXT_MAJOR_VERSION_ARB, attribs.majorVersion,
342         GLX_CONTEXT_MINOR_VERSION_ARB, attribs.minorVersion
343     ];
344     if (attribs.decimalVersion >= 32) {
345         ctxAttribs ~= GLX_CONTEXT_PROFILE_MASK_ARB;
346         if (attribs.profile == GlProfile.core) {
347             ctxAttribs ~= GLX_CONTEXT_CORE_PROFILE_BIT_ARB;
348         }
349         else {
350             ctxAttribs ~= GLX_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB;
351         }
352     }
353 
354     return ctxAttribs ~ 0;
355 }