1 module gfx.window.xcb.context;
2 
3 version(linux):
4 
5 import gfx.bindings.opengl.loader : SharedLib;
6 import gfx.gl3.context : GlAttribs, GlContext, glVersions;
7 import X11.Xlib : XDisplay = Display, XErrorEvent;
8 
9 import gfx.window.xcb : gfxXcbLog;
10 
11 /// GlX backed OpenGL context
12 class XcbGlContext : GlContext
13 {
14     import gfx.bindings.opengl.loader : SharedSym;
15     import gfx.bindings.opengl.gl : Gl;
16     import gfx.bindings.opengl.glx : Glx, GLXContext, GLXFBConfig;
17     import gfx.core.rc : atomicRcCode, Disposable;
18 
19     mixin(atomicRcCode);
20 
21     private XDisplay* _dpy;
22     private int _mainScreenNum;
23     private GlAttribs _attribs;
24     private Glx _glx;
25     private Gl _gl;
26     private string[] _glxAvailExts;
27     private string[] _glAvailExts;
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     /// The window is necessary to make the context current on and loading GL symbols.
36     /// It can be a dummy window destroyed right initialization.
37     this (XDisplay* dpy, in int mainScreenNum, in GlAttribs attribs, in size_t window)
38     {
39         import gfx.bindings.opengl.loader : openSharedLib, loadSharedSym, SharedLib;
40         import gfx.bindings.opengl.gl : GL_EXTENSIONS;
41         import gfx.bindings.opengl.glx : PFN_glXGetProcAddressARB;
42         import gfx.bindings.opengl.util : splitExtString;
43         import std.algorithm : canFind;
44         import std.exception : enforce;
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         GlAttribs attrs = attribs;
70 
71         auto oldHandler = XSetErrorHandler(&createCtxErrorHandler);
72 
73         foreach (const glVer; glVersions) {
74             attrs.majorVersion = glVer / 10;
75             attrs.minorVersion = glVer % 10;
76             if (attrs.decimalVersion < attribs.decimalVersion) break;
77 
78             const ctxAttribs = getCtxAttribs(attrs);
79             gfxXcbLog.tracef("attempting to create OpenGL %s.%s context", attrs.majorVersion, attrs.minorVersion);
80 
81             createContextErrorFlag = false;
82             _ctx = _glx.CreateContextAttribsARB(_dpy, fbc, null, 1, &ctxAttribs[0]);
83 
84             if (_ctx && !createContextErrorFlag) break;
85         }
86 
87         XSetErrorHandler(oldHandler);
88 
89         enforce(_ctx);
90         XSync(_dpy, 0);
91         _attribs = attrs;
92 
93         gfxXcbLog.tracef("created OpenGL %s.%s context", attrs.majorVersion, attrs.minorVersion);
94 
95         XcbGlContext.makeCurrent(window);
96         _gl = new Gl(&loadSymbol);
97 
98         gfxXcbLog.trace("done loading GL/GLX");
99     }
100 
101     override void dispose() {
102         import gfx.bindings.opengl.loader : closeSharedLib;
103 
104         _glx.DestroyContext(_dpy, _ctx);
105         gfxXcbLog.trace("destroyed GL/GLX context");
106     }
107 
108 
109     override @property Gl gl() {
110         return _gl;
111     }
112 
113     override @property GlAttribs attribs() {
114         return _attribs;
115     }
116 
117     override bool makeCurrent(size_t nativeHandle)
118     {
119         import gfx.bindings.opengl.glx : GLXDrawable;
120         return _glx.MakeCurrent(_dpy, cast(GLXDrawable)nativeHandle, _ctx) != 0;
121     }
122 
123     override void doneCurrent()
124     {
125         _glx.MakeCurrent(_dpy, 0, null);
126     }
127 
128     override @property bool current() const
129     {
130         return _glx.GetCurrentContext() is _ctx;
131     }
132 
133     override @property int swapInterval()
134     {
135         import gfx.bindings.opengl.glx : GLXDrawable;
136         if (MESA_swap_control) {
137             return _glx.GetSwapIntervalMESA();
138         }
139         else if (EXT_swap_control) {
140             GLXDrawable drawable = _glx.GetCurrentDrawable();
141             uint swap;
142 
143             if (drawable) {
144                 import gfx.bindings.opengl.glx : GLX_SWAP_INTERVAL_EXT;
145                 _glx.QueryDrawable(_dpy, drawable, GLX_SWAP_INTERVAL_EXT, &swap);
146                 return swap;
147             }
148             else {
149                 gfxXcbLog.warning("could not get glx drawable to get swap interval");
150                 return -1;
151             }
152 
153         }
154         return -1;
155     }
156 
157     override @property void swapInterval(int interval)
158     {
159         import gfx.bindings.opengl.glx : GLXDrawable;
160 
161         if (MESA_swap_control) {
162             _glx.SwapIntervalMESA(interval);
163         }
164         else if (EXT_swap_control) {
165             GLXDrawable drawable = _glx.GetCurrentDrawable();
166 
167             if (drawable) {
168                 _glx.SwapIntervalEXT(_dpy, drawable, interval);
169             }
170             else {
171                 gfxXcbLog.warning("could not get glx drawable to set swap interval");
172             }
173         }
174     }
175 
176     override void swapBuffers(size_t nativeHandle)
177     {
178         import gfx.bindings.opengl.glx : GLXDrawable;
179         _glx.SwapBuffers(_dpy, cast(GLXDrawable)nativeHandle);
180     }
181 
182     private GLXFBConfig getGlxFBConfig(in GlAttribs attribs)
183     {
184         import X11.Xlib : XFree;
185 
186         const glxAttribs = getGlxAttribs(attribs);
187 
188         int numConfigs;
189         auto fbConfigs = _glx.ChooseFBConfig(_dpy, _mainScreenNum, &glxAttribs[0], &numConfigs);
190 
191         if (!fbConfigs || !numConfigs)
192         {
193             gfxXcbLog.error("GFX-GLX: could not get fb config");
194             return null;
195         }
196         scope (exit) XFree(fbConfigs);
197 
198         return fbConfigs[0];
199     }
200 
201 }
202 
203 
204 private SharedLib loadGlLib()
205 {
206     import gfx.bindings.opengl.loader : openSharedLib;
207 
208     immutable glLibNames = ["libGL.so.1", "libGL.so"];
209 
210     foreach (ln; glLibNames) {
211         auto lib = openSharedLib(ln);
212         if (lib) {
213             gfxXcbLog.tracef("opening shared library %s", ln);
214             return lib;
215         }
216     }
217 
218     import std.conv : to;
219     throw new Exception("could not load any of these libraries: " ~ glLibNames.to!string);
220 }
221 
222 private bool createContextErrorFlag;
223 
224 extern(C) private int createCtxErrorHandler(XDisplay *dpy, XErrorEvent *error)
225 {
226    createContextErrorFlag = true;
227    return 0;
228 }
229 
230 private int[] getGlxAttribs(in GlAttribs attribs) pure
231 {
232     import gfx.bindings.opengl.glx;
233     import gfx.graal.format : formatDesc, redBits, greenBits, blueBits,
234                               alphaBits, depthBits, stencilBits;
235 
236     int[] glxAttribs = [
237         GLX_X_RENDERABLE,   1,
238         GLX_DRAWABLE_TYPE,  GLX_WINDOW_BIT,
239         GLX_RENDER_TYPE,    GLX_RGBA_BIT,
240         GLX_X_VISUAL_TYPE,  GLX_TRUE_COLOR
241     ];
242 
243     const colorDesc = formatDesc(attribs.colorFormat);
244     const depthStencilDesc = formatDesc(attribs.depthStencilFormat);
245 
246     const r = redBits(colorDesc.surfaceType);
247     const g = greenBits(colorDesc.surfaceType);
248     const b = blueBits(colorDesc.surfaceType);
249     const a = alphaBits(colorDesc.surfaceType);
250     const d = depthBits(depthStencilDesc.surfaceType);
251     const s = stencilBits(depthStencilDesc.surfaceType);
252 
253     if (r) glxAttribs ~= [GLX_RED_SIZE, r];
254     if (g) glxAttribs ~= [GLX_GREEN_SIZE, g];
255     if (b) glxAttribs ~= [GLX_BLUE_SIZE, b];
256     if (a) glxAttribs ~= [GLX_ALPHA_SIZE, a];
257     if (d) glxAttribs ~= [GLX_DEPTH_SIZE, d];
258     if (s) glxAttribs ~= [GLX_STENCIL_SIZE, s];
259 
260     if (attribs.doublebuffer) glxAttribs ~= [GLX_DOUBLEBUFFER, 1];
261 
262     if (attribs.samples > 1)
263         glxAttribs ~= [GLX_SAMPLE_BUFFERS, 1, GLX_SAMPLES, attribs.samples];
264 
265     return glxAttribs ~ 0;
266 }
267 
268 private int[] getCtxAttribs(in GlAttribs attribs) pure
269 {
270     import gfx.bindings.opengl.glx;
271     import gfx.gl3.context : GlProfile;
272 
273     int[] ctxAttribs = [
274         GLX_CONTEXT_MAJOR_VERSION_ARB, attribs.majorVersion,
275         GLX_CONTEXT_MINOR_VERSION_ARB, attribs.minorVersion
276     ];
277     if (attribs.decimalVersion >= 32) {
278         ctxAttribs ~= GLX_CONTEXT_PROFILE_MASK_ARB;
279         if (attribs.profile == GlProfile.core) {
280             ctxAttribs ~= GLX_CONTEXT_CORE_PROFILE_BIT_ARB;
281         }
282         else {
283             ctxAttribs ~= GLX_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB;
284         }
285     }
286 
287     return ctxAttribs ~ 0;
288 }