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     }
47     else static assert(0, "Platform Problem!!!");
48 }
49 
50 @nogc nothrow:
51 
52 package GLSupport getContextVersion(SharedLib lib)
53 {
54     // Lazy load the appropriate symbols for fetching the current context
55     // and getting OpenGL symbols from it.
56     if(getCurrentContext == null) {
57         lib.bindSymbol(cast(void**)&getCurrentContext, getCurrentContextName);
58         if(getCurrentContext == null) return GLSupport.badLibrary;
59 
60         version(OSX) { /* Nothing to do */ }
61         else {
62             lib.bindSymbol(cast(void**)&getProcAddress, getProcAddressName);
63             if(getProcAddress == null) return GLSupport.badLibrary;
64         }
65     }
66 
67     // Check if a context is current
68     if(getCurrentContext() == null) return GLSupport.noContext;
69 
70     // Lazy load glGetString to check the context version
71     if(getString == null) {
72         lib.bindSymbol(cast(void**)&getString, "glGetString");
73         if(getString == null) return GLSupport.badLibrary;
74     }
75 
76     /* glGetString(GL_VERSION) is guaranteed to return a constant string
77       of the format "[major].[minor].[build] xxxx", where xxxx is vendor-specific
78       information. Here, I'm pulling two characters out of the string, the major
79       and minor version numbers. */
80     auto verstr = getString(glVersion);
81     char major = *verstr;
82     char minor = *(verstr + 2);
83 
84     GLSupport support = GLSupport.noLibrary;
85 
86     switch(major) {
87         case '4':
88             if(minor == '5') support = GLSupport.gl45;
89             else if(minor == '4') support = GLSupport.gl44;
90             else if(minor == '3') support = GLSupport.gl43;
91             else if(minor == '2') support = GLSupport.gl42;
92             else if(minor == '1') support = GLSupport.gl41;
93             else if(minor == '0') support = GLSupport.gl40;
94 
95             /* No default condition here, since it's possible for new
96              minor versions of the 4.x series to be released before
97              support is added. That case is handled outside
98              of the switch. When no more 4.x versions are released, this
99              should be changed to return GL40 by default. */
100             break;
101 
102         case '3':
103             if(minor == '3') support = GLSupport.gl33;
104             else if(minor == '2') support = GLSupport.gl32;
105             else if(minor == '1') support = GLSupport.gl31;
106             else support = GLSupport.gl30;
107             break;
108 
109         case '2':
110             if(minor == '1') support = GLSupport.gl21;
111             else support = GLSupport.gl20;
112             break;
113 
114         case '1':
115             if(minor == '5') support = GLSupport.gl15;
116             else if(minor == '4') support = GLSupport.gl14;
117             else if(minor == '3') support = GLSupport.gl13;
118             else if(minor == '2') support = GLSupport.gl12;
119             else support = GLSupport.gl11;
120             break;
121 
122         default:
123             /* glGetString(GL_VERSION) is guaranteed to return a result
124              of a specific format, so if this point is reached it is
125              going to be because a major version higher than what BindBC
126              supports was encountered. That case is handled outside the
127              switch. */
128             break;
129     }
130 
131     // If support hasn't yet been set, it means the context has a higher
132     // version than the binding knows about. Set to the compile-time version.
133     if(support == GLSupport.noLibrary) support = glSupport;
134 
135     // For contexts >= 3.0, make sure glGetStringi & glGetIntegerv are avaliable.
136     if(support >= GLSupport.gl30) {
137         lib.bindGLSymbol(cast(void**)&getStringi, "glGetStringi");
138 
139         // Use bindSymbol here, since it's a base function
140         lib.bindSymbol(cast(void**)&getIntegerv, "glGetIntegerv");
141 
142         if(getStringi == null || getIntegerv == null)
143             return GLSupport.badLibrary;
144     }
145 
146     return support;
147 }
148 
149 private uint numErrors;
150 
151 package(bindbc.opengl):
152 
153 uint errorCountGL() { return numErrors; }
154 
155 bool resetErrorCountGL()
156 {
157     if(numErrors != 0) {
158         numErrors = 0;
159         return false;
160     }
161     else return true;
162 }
163 
164 void bindGLSymbol(SharedLib lib, void** ptr, const(char)* symName)
165 {
166     // Use dlopen on Mac
167     version(OSX) {
168         auto startErrorCount = errorCount();
169         lib.bindSymbol(ptr, symName);
170         numErrors += errorCount() - startErrorCount;
171     }
172     else {
173         *ptr = getProcAddress(symName);
174         if(*ptr == null) ++numErrors;
175     }
176 }
177 
178 bool hasExtension(GLSupport contextVersion, const(char)* extName)
179 {
180     import core.stdc.string : strcmp, strstr, strlen;
181 
182     // With a modern context, use the modern approach
183     if(contextVersion >= GLSupport.gl30) {
184         int count;
185         getIntegerv(glNumExtensions, &count);
186 
187         const(char)* ext;
188         for(int i=0; i<count; ++i) {
189             ext = getStringi(glExtensions, i);
190             if(ext && strcmp(ext, extName) == 0) return true;
191         }
192     }
193     // Otherwise, use the classic approach
194     else {
195         auto extstr = getString(glExtensions);
196         if(!extstr) return false;
197 
198         auto len = strlen(extName);
199         auto ext = strstr(extstr, extName);
200         while(ext) {
201             /* It's possible that the extension name is actually a substring of
202              another extension. If not, then the character following the name in
203              the extension string should be a space (or possibly the null character).
204             */
205             if(ext[len] == ' ' || ext[len] == '\0') return true;
206             ext = strstr(ext + len, extName);
207         }
208     }
209 
210     return false;
211 }