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