1 
2 //          Copyright 2018 - 2021 Michael D. Parker
3 // Distributed under the Boost Software License, Version 1.0.
4 //    (See accompanying file LICENSE_1_0.txt or copy at
5 //          http://www.boost.org/LICENSE_1_0.txt)
6 
7 module bindbc.opengl.context;
8 
9 import bindbc.loader;
10 import bindbc.opengl.config;
11 
12 private {
13     enum uint glVersion = 0x1F02;
14     enum uint glExtensions = 0x1F03;
15     enum uint glNumExtensions = 0x821D;
16     extern(System) @nogc nothrow {
17         alias GetString = const(char)* function(uint);
18         alias GetStringi = const(char)* function(uint,uint);
19         alias GetIntegerv = void function(uint, int*);
20     }
21     __gshared {
22         GetString getString;
23         GetStringi getStringi;
24         GetIntegerv getIntegerv;
25     }
26 
27     extern(System) @nogc nothrow {
28         alias GetCurrentContext = void* function();
29         alias GetProcAddress = void* function(const(char)*);
30     }
31     __gshared {
32         GetCurrentContext getCurrentContext;
33         GetProcAddress getProcAddress;
34     }
35 
36     version(Windows) {
37         enum getCurrentContextName = "wglGetCurrentContext";
38         enum getProcAddressName = "wglGetProcAddress";
39     }
40     else version(OSX) {
41         enum getCurrentContextName = "CGLGetCurrentContext";
42     }
43     else version(Posix) {
44         enum getCurrentContextName = "glXGetCurrentContext";
45         enum getProcAddressName = "glXGetProcAddress";
46         enum getCurrentContextNameEGL = "eglGetCurrentContext";
47         enum getProcAddressNameEGL = "eglGetProcAddress";
48         enum sessionTypeEnv = "XDG_SESSION_TYPE";
49 
50         const(char)[][2] eglNames = [
51             "libEGL.so.1",
52             "libEGL.so"
53         ];
54 
55         SharedLib libEGL;
56     }
57     else static assert(0, "Platform Problem!!!");
58 }
59 
60 @nogc nothrow:
61 
62 package:
63 version(Posix) void unloadContext()
64 {
65     version (OSX) { /* Nothing to do */ }
66     else {
67         if(libEGL != invalidHandle) {
68             libEGL.unload();
69             libEGL = invalidHandle;
70         }
71     }
72 }
73 
74 GLSupport getContextVersion(SharedLib lib)
75 {
76     // Lazy load the appropriate symbols for fetching the current context
77     // and getting OpenGL symbols from it.
78     if(getCurrentContext == null) {
79         bool loaded = false;
80 
81         // On Posix systems other than OSX, check if we're running under Wayland,
82         // and load the appropriate symbols from libEGL if so.
83         version(Posix) {
84             version(OSX) { /* Nothing to do */ }
85             else {
86                 import core.stdc.string : strcmp;
87                 import core.stdc.stdlib : getenv;
88                 char* sessionType = getenv(sessionTypeEnv);
89                 if(sessionType != null && strcmp(sessionType, "wayland") == 0) {
90                     if(libEGL == invalidHandle) {
91                         foreach(libName; eglNames) {
92                             libEGL = load(libName.ptr);
93                             if(libEGL != invalidHandle) break;
94                         }
95 
96                         if(libEGL != invalidHandle) {
97                             libEGL.bindSymbol(cast(void**)&getCurrentContext, getCurrentContextNameEGL);
98                             libEGL.bindSymbol(cast(void**)&getProcAddress, getProcAddressNameEGL);
99 
100                             /*
101                               If the symbols were loaded successfully, that's not enough. For example, on Ubuntu 22.04
102                               using GLFW configured for X11, eglGetCurrentContext returns null, but glXGetCurrentContext
103                               returns a valid pointer, even though we're in a Wayland session. So here, if getCurrentContext
104                               returns null, then that means one of things: either no context has been created, or it was 
105                               created through X. So only set loaded to true if getCurrentContext returns non-null. Then
106                               then a second attempt can be made via the glX stuff.
107                             */
108                             if(getCurrentContext != null && getProcAddress != null && getCurrentContext() != null) {
109                                 loaded = true;
110                             }
111                             else {
112                                 unloadContext();
113                                 getCurrentContext = null;
114                                 getProcAddress = null;
115                             }
116                         }
117                         else return GLSupport.noLibrary;
118                     }
119                 }
120             }
121         }
122 
123         /*
124           If loaded is false at this point, then one of four things is true:
125             1. We aren't on Posix 
126             2. We're on Posix in an X11 session 
127             3. We're on Posix in a Wayland session, but the context was created through X 
128             4. We're on Posix in a Wayland session, but no context has been created 
129         */
130         if(!loaded) {
131             lib.bindSymbol(cast(void**)&getCurrentContext, getCurrentContextName);
132             if(getCurrentContext == null) return GLSupport.badLibrary;
133 
134             version(OSX) { /* Nothing to do */ }
135             else {
136                 lib.bindSymbol(cast(void**)&getProcAddress, getProcAddressName);
137                 if(getProcAddress == null) return GLSupport.badLibrary;
138             }
139         }
140     }
141 
142     // Check if a context is current
143     if(getCurrentContext() == null) return GLSupport.noContext;
144 
145     // Lazy load glGetString to check the context version
146     if(getString == null) {
147         lib.bindSymbol(cast(void**)&getString, "glGetString");
148         if(getString == null) return GLSupport.badLibrary;
149     }
150 
151     /* glGetString(GL_VERSION) is guaranteed to return a constant string
152       of the format "[major].[minor].[build] xxxx", where xxxx is vendor-specific
153       information. Here, I'm pulling two characters out of the string, the major
154       and minor version numbers. */
155     auto verstr = getString(glVersion);
156     char major = *verstr;
157     char minor = *(verstr + 2);
158 
159     GLSupport support = GLSupport.noLibrary;
160 
161     switch(major) {
162         case '4':
163             if(minor == '6') support = GLSupport.gl46;
164             else if(minor == '5') support = GLSupport.gl45;
165             else if(minor == '4') support = GLSupport.gl44;
166             else if(minor == '3') support = GLSupport.gl43;
167             else if(minor == '2') support = GLSupport.gl42;
168             else if(minor == '1') support = GLSupport.gl41;
169             else if(minor == '0') support = GLSupport.gl40;
170 
171             /* No default condition here, since it's possible for new
172              minor versions of the 4.x series to be released before
173              support is added. That case is handled outside
174              of the switch. When no more 4.x versions are released, this
175              should be changed to return GL40 by default. */
176             break;
177 
178         case '3':
179             if(minor == '3') support = GLSupport.gl33;
180             else if(minor == '2') support = GLSupport.gl32;
181             else if(minor == '1') support = GLSupport.gl31;
182             else support = GLSupport.gl30;
183             break;
184 
185         case '2':
186             if(minor == '1') support = GLSupport.gl21;
187             else support = GLSupport.gl20;
188             break;
189 
190         case '1':
191             if(minor == '5') support = GLSupport.gl15;
192             else if(minor == '4') support = GLSupport.gl14;
193             else if(minor == '3') support = GLSupport.gl13;
194             else if(minor == '2') support = GLSupport.gl12;
195             else support = GLSupport.gl11;
196             break;
197 
198         default:
199             /* glGetString(GL_VERSION) is guaranteed to return a result
200              of a specific format, so if this point is reached it is
201              going to be because a major version higher than what BindBC
202              supports was encountered. That case is handled outside the
203              switch. */
204             break;
205     }
206 
207     // If support hasn't yet been set, it means the context has a higher
208     // version than the binding knows about. Set to the compile-time version.
209     if(support == GLSupport.noLibrary) support = glSupport;
210 
211     // For contexts >= 3.0, make sure glGetStringi & glGetIntegerv are avaliable.
212     if(support >= GLSupport.gl30) {
213         lib.bindGLSymbol(cast(void**)&getStringi, "glGetStringi");
214 
215         // Use bindSymbol here, since it's a base function
216         lib.bindSymbol(cast(void**)&getIntegerv, "glGetIntegerv");
217 
218         if(getStringi == null || getIntegerv == null)
219             return GLSupport.badLibrary;
220     }
221 
222     return support;
223 }
224 
225 private uint numErrors;
226 
227 package(bindbc.opengl):
228 
229 uint errorCountGL() { return numErrors; }
230 
231 bool resetErrorCountGL()
232 {
233     if(numErrors != 0) {
234         numErrors = 0;
235         return false;
236     }
237     else return true;
238 }
239 
240 void bindGLSymbol(SharedLib lib, void** ptr, const(char)* symName)
241 {
242     // Use dlopen on Mac
243     version(OSX) {
244         auto startErrorCount = errorCount();
245         lib.bindSymbol(ptr, symName);
246         numErrors += errorCount() - startErrorCount;
247     }
248     else {
249         *ptr = getProcAddress(symName);
250         if(*ptr == null) ++numErrors;
251     }
252 }
253 
254 bool hasExtension(GLSupport contextVersion, const(char)* extName)
255 {
256     import core.stdc.string : strcmp, strstr, strlen;
257 
258     // With a modern context, use the modern approach
259     if(contextVersion >= GLSupport.gl30) {
260         int count;
261         getIntegerv(glNumExtensions, &count);
262 
263         const(char)* ext;
264         for(int i=0; i<count; ++i) {
265             ext = getStringi(glExtensions, i);
266             if(ext && strcmp(ext, extName) == 0) return true;
267         }
268     }
269     // Otherwise, use the classic approach
270     else {
271         auto extstr = getString(glExtensions);
272         if(!extstr) return false;
273 
274         auto len = strlen(extName);
275         auto ext = strstr(extstr, extName);
276         while(ext) {
277             /* It's possible that the extension name is actually a substring of
278              another extension. If not, then the character following the name in
279              the extension string should be a space (or possibly the null character).
280             */
281             if(ext[len] == ' ' || ext[len] == '\0') return true;
282             ext = strstr(ext + len, extName);
283         }
284     }
285 
286     return false;
287 }