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 }