1 module gfm.opengl.opengl;
2 
3 import core.stdc.stdlib;
4 
5 import std..string,
6        std.conv,
7        std.array,
8        std.algorithm;
9 
10 import bindbc.opengl;
11 
12 import std.experimental.logger;
13 
14 /// The one exception type thrown in this wrapper.
15 /// A failing OpenGL function should <b>always</b> throw an $(D OpenGLException).
16 class OpenGLException : Exception
17 {
18     public
19     {
20         @safe pure nothrow this(string message, string file =__FILE__, size_t line = __LINE__, Throwable next = null)
21         {
22             super(message, file, line, next);
23         }
24     }
25 }
26 
27 /// This object is passed around to other OpenGL wrapper objects
28 /// to ensure library loading.
29 /// Create one to use OpenGL.
30 final class OpenGL
31 {
32     public
33     {
34         enum Vendor
35         {
36             AMD,
37             Apple, // for software rendering aka no driver
38             Intel,
39             Mesa,
40             Microsoft, // for "GDI generic" aka no driver
41             NVIDIA,
42             other
43         }
44 
45         /// Load OpenGL library, redirect debug output to our logger.
46         /// You can pass a null logger if you don't want logging.
47         /// Throws: $(D OpenGLException) on error.
48         this(Logger logger)
49         {
50             _logger = logger is null ? new NullLogger() : logger;
51 
52             const ret = loadOpenGL();
53             if(ret < glSupport)
54             {
55                 if(ret == GLSupport.noLibrary)
56                     throw new OpenGLException("OpenGL shared library failed to load");
57                 else if(GLSupport.badLibrary)
58                     // One or more symbols failed to load. The likely cause is that the
59                     // shared library is for a lower version than bindbc-sdl was configured
60                     // to load (via SDL_201, SDL_202, etc.)
61                     throw new OpenGLException("One or more symbols of OpenGL shared library failed to load");
62                 else
63                     throw new OpenGLException("The version of the OpenGL library on your system is too low. Please upgrade.");
64             }
65 
66             getLimits(false);
67         }
68 
69         /// Returns: true if the OpenGL extension is supported.
70         bool supportsExtension(string extension)
71         {
72             foreach(s; _extensions)
73                 if (s == extension)
74                     return true;
75             return false;
76         }
77 
78         /// Redirects OpenGL debug output to the Logger.
79         /// You still has to use glDebugMessageControl to set which messages are emitted.
80         /// For example, to enable all messages, use:
81         /// glDebugMessageControl(GL_DONT_CARE, GL_DONT_CARE, GL_DONT_CARE, 0, null, GL_TRUE);
82         void redirectDebugOutput()
83         {
84             if (glDebugMessageCallback !is null)
85             {
86                 glDebugMessageCallback(&loggingCallbackOpenGL, cast(void*)this);
87                 runtimeCheck();
88                 glEnable(GL_DEBUG_OUTPUT);
89                 runtimeCheck();
90             }
91         }
92 
93         /// Check for pending OpenGL errors, log a message if there is.
94         /// Only for debug purpose since this check will be disabled in a release build.
95         void debugCheck()
96         {
97             debug
98             {
99                 GLint r = glGetError();
100                 if (r != GL_NO_ERROR)
101                 {
102                     flushGLErrors(); // flush other errors if any
103                     _logger.errorf("OpenGL error: %s", getErrorString(r));
104                     assert(false); // break here
105                 }
106             }
107         }
108 
109         /// Checks pending OpenGL errors.
110         /// Returns: true if at least one OpenGL error was pending. OpenGL error status is cleared.
111         bool runtimeCheckNothrow() nothrow
112         {
113             GLint r = glGetError();
114             if (r != GL_NO_ERROR)
115             {
116                 flushGLErrors(); // flush other errors if any
117                 return false;
118             }
119             return true;
120         }
121 
122         /// Returns: OpenGL string returned by $(D glGetString).
123         /// See_also: $(WEB www.opengl.org/sdk/docs/man/xhtml/glGetString.xml)
124         const(char)[] getString(GLenum name)
125         {
126             const(char)* sZ = glGetString(name);
127             runtimeCheck();
128             if (sZ is null)
129                 return "(unknown)";
130             else
131                 return fromStringz(sZ);
132         }
133 
134         /// Returns: OpenGL string returned by $(D glGetStringi)
135         /// See_also: $(WEB www.opengl.org/sdk.docs/man/xhtml/glGetString.xml)
136         const(char)[] getString(GLenum name, GLuint index)
137         {
138             const(char)* sZ = glGetStringi(name, index);
139             runtimeCheck();
140             if (sZ is null)
141                 return "(unknown)";
142             else
143                 return fromStringz(sZ);
144         }
145 
146         /// Returns: OpenGL major version.
147         int getMajorVersion() pure const nothrow @nogc
148         {
149             return _majorVersion;
150         }
151 
152         /// Returns: OpenGL minor version.
153         int getMinorVersion() pure const nothrow @nogc
154         {
155             return _minorVersion;
156         }
157 
158         /// Returns: OpenGL version string, can be "major_number.minor_number" or
159         ///          "major_number.minor_number.release_number", eventually
160         ///          followed by a space and additional vendor informations.
161         /// See_also: $(WEB www.opengl.org/sdk/docs/man/xhtml/glGetString.xml)
162         const(char)[] getVersionString()
163         {
164             return getString(GL_VERSION);
165         }
166 
167         /// Returns: The company responsible for this OpenGL implementation, so
168         ///          that you can plant a giant toxic mushroom below their office.
169         const(char)[] getVendorString()
170         {
171             return getString(GL_VENDOR);
172         }
173 
174         /// Tries to detect the driver maker.
175         /// Returns: Identified vendor.
176         Vendor getVendor()
177         {
178             const(char)[] s = getVendorString();
179             if (canFind(s, "AMD") || canFind(s, "ATI") || canFind(s, "Advanced Micro Devices"))
180                 return Vendor.AMD;
181             else if (canFind(s, "NVIDIA") || canFind(s, "nouveau") || canFind(s, "Nouveau"))
182                 return Vendor.NVIDIA;
183             else if (canFind(s, "Intel"))
184                 return Vendor.Intel;
185             else if (canFind(s, "Mesa"))
186                 return Vendor.Mesa;
187             else if (canFind(s, "Microsoft"))
188                 return Vendor.Microsoft;
189             else if (canFind(s, "Apple"))
190                 return Vendor.Apple;
191             else
192                 return Vendor.other;
193         }
194 
195         /// Returns: Name of the renderer. This name is typically specific
196         ///          to a particular configuration of a hardware platform.
197         const(char)[] getRendererString()
198         {
199             return getString(GL_RENDERER);
200         }
201 
202         /// Returns: GLSL version string, can be "major_number.minor_number" or
203         ///          "major_number.minor_number.release_number".
204         const(char)[] getGLSLVersionString()
205         {
206             return getString(GL_SHADING_LANGUAGE_VERSION);
207         }
208 
209         /// Returns: A slice made up of available extension names.
210         string[] getExtensions() pure nothrow @nogc
211         {
212             return _extensions;
213         }
214 
215         /// Calls $(D glGetIntegerv) and gives back the requested integer.
216         /// Returns: true if $(D glGetIntegerv) succeeded.
217         /// See_also: $(WEB www.opengl.org/sdk/docs/man4/xhtml/glGet.xml).
218         /// Note: It is generally a bad idea to call $(D glGetSomething) since it might stall
219         ///       the OpenGL pipeline.
220         static int getInteger(GLenum pname)
221         {
222             GLint param;
223             glGetIntegerv(pname, &param);
224             .runtimeCheck();
225             return param;
226         }
227 
228 
229         /// Returns: The requested float returned by $(D glGetFloatv).
230         /// See_also: $(WEB www.opengl.org/sdk/docs/man4/xhtml/glGet.xml).
231         /// Throws: $(D OpenGLException) if at least one OpenGL error was pending.
232         static float getFloat(GLenum pname)
233         {
234             GLfloat res;
235             glGetFloatv(pname, &res);
236             .runtimeCheck();
237             return res;
238         }
239     }
240 
241     package
242     {
243         Logger _logger;
244 
245     }
246 
247     private
248     {
249         string[] _extensions;
250         int _majorVersion;
251         int _minorVersion;
252         int _maxColorAttachments;
253 
254         public void getLimits(bool isReload)
255         {
256             // parse GL_VERSION string
257             if (isReload)
258             {
259                 const(char)[] verString = getVersionString();
260 
261                 // "Vendor-specific information may follow the version number.
262                 // Its format depends on the implementation, but a space always
263                 // separates the version number and the vendor-specific information."
264                 // Consequently we must slice the version string up to the first space.
265                 // Thanks to @ColonelThirtyTwo for reporting this.
266                 int firstSpace = cast(int)countUntil(verString, " ");
267                 if (firstSpace != -1)
268                     verString = verString[0..firstSpace];
269 
270                 const(char)[][] verParts = std.array.split(verString, ".");
271 
272                 if (verParts.length < 2)
273                 {
274                     cant_parse:
275                     _logger.warning(format("Couldn't parse GL_VERSION string '%s', assuming OpenGL 1.1", verString));
276                     _majorVersion = 1;
277                     _minorVersion = 1;
278                 }
279                 else
280                 {
281                     try
282                         _majorVersion = to!int(verParts[0]);
283                     catch (Exception e)
284                         goto cant_parse;
285 
286                     try
287                         _minorVersion = to!int(verParts[1]);
288                     catch (Exception e)
289                         goto cant_parse;
290                 }
291 
292                 // 2. Get a list of available extensions
293                 if (_majorVersion < 3)
294                 {
295                     // Legacy way to get extensions
296                     _extensions = std.array.split(getString(GL_EXTENSIONS).idup);
297                 }
298                 else
299                 {
300                     // New way to get extensions
301                     int numExtensions = getInteger(GL_NUM_EXTENSIONS);
302                     _extensions.length = 0;
303                     for (int i = 0; i < numExtensions; ++i)
304                         _extensions ~= getString(GL_EXTENSIONS, i).idup;
305                 }
306 
307                 // 3. Get limits
308                 _maxColorAttachments = getInteger(GL_MAX_COLOR_ATTACHMENTS);
309             }
310             else
311             {
312                 _majorVersion = 1;
313                 _minorVersion = 1;
314                 _extensions = [];
315                 _maxColorAttachments = 0;
316             }
317         }
318     }
319 
320     /// Checks pending OpenGL errors.
321     /// Throws: $(D OpenGLException) if at least one OpenGL error was pending.
322     void runtimeCheck()
323     {
324         .runtimeCheck();
325     }
326 }
327 
328 private string getErrorString(GLint r) pure nothrow
329 {
330     switch(r)
331     {
332         case GL_NO_ERROR:          return "GL_NO_ERROR";
333         case GL_INVALID_ENUM:      return "GL_INVALID_ENUM";
334         case GL_INVALID_VALUE:     return "GL_INVALID_VALUE";
335         case GL_INVALID_OPERATION: return "GL_INVALID_OPERATION";
336         case GL_OUT_OF_MEMORY:     return "GL_OUT_OF_MEMORY";
337         default:                   return "Unknown OpenGL error";
338     }
339 }
340 
341 // flush out OpenGL errors
342 private void flushGLErrors() nothrow
343 {
344     int timeout = 0;
345     while (++timeout <= 5) // avoid infinite loop in a no-driver situation
346     {
347         GLint r = glGetError();
348         if (r == GL_NO_ERROR)
349             break;
350     }
351 }
352 
353 /// Checks pending OpenGL errors.
354 /// Throws: $(D OpenGLException) if at least one OpenGL error was pending.
355 void runtimeCheck()
356 {
357     GLint r = glGetError();
358     if (r != GL_NO_ERROR)
359     {
360         string errorString = getErrorString(r);
361         flushGLErrors(); // flush other errors if any
362         throw new OpenGLException(errorString);
363     }
364 }
365 
366 /// Sets the "active texture" which is more precisely active texture unit.
367 /// Throws: $(D OpenGLException) on error.
368 void setActiveTexture(int texture)
369 {
370     glActiveTexture(GL_TEXTURE0 + texture);
371     runtimeCheck();
372 }
373 
374 /// Returns: Maximum number of color attachments. This is the number of targets a fragment shader can output to.
375 /// You can rely on this number being at least 4 if MRT is supported.
376 int maxColorAttachments()
377 {
378     return OpenGL.getInteger(GL_MAX_COLOR_ATTACHMENTS);
379 }
380 
381 extern(System) private
382 {
383     // This callback can be called from multiple threads
384     nothrow void loggingCallbackOpenGL(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const(GLchar)* message, GLvoid* userParam)
385     {
386         try
387         {
388             OpenGL opengl = cast(OpenGL)userParam;
389 
390             try
391             {
392                 Logger logger = opengl._logger;
393 
394                 string ssource;
395                 switch (source)
396                 {
397                     case GL_DEBUG_SOURCE_API:             ssource = "API"; break;
398                     case GL_DEBUG_SOURCE_WINDOW_SYSTEM:   ssource = "window system"; break;
399                     case GL_DEBUG_SOURCE_SHADER_COMPILER: ssource = "shader compiler"; break;
400                     case GL_DEBUG_SOURCE_THIRD_PARTY:     ssource = "third party"; break;
401                     case GL_DEBUG_SOURCE_APPLICATION:     ssource = "application"; break;
402                     case GL_DEBUG_SOURCE_OTHER:           ssource = "other"; break;
403                     default:                              ssource= "unknown"; break;
404                 }
405 
406                 string stype;
407                 switch (type)
408                 {
409                     case GL_DEBUG_TYPE_ERROR:               stype = "error"; break;
410                     case GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR: stype = "deprecated behaviour"; break;
411                     case GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR:  stype = "undefined behaviour"; break;
412                     case GL_DEBUG_TYPE_PORTABILITY:         stype = "portabiliy"; break;
413                     case GL_DEBUG_TYPE_PERFORMANCE:         stype = "performance"; break;
414                     case GL_DEBUG_TYPE_OTHER:               stype = "other"; break;
415                     default:                                stype = "unknown"; break;
416                 }
417 
418                 LogLevel level;
419 
420                 string sseverity;
421                 switch (severity)
422                 {
423                     case GL_DEBUG_SEVERITY_HIGH:
424                         level = LogLevel.error;
425                         sseverity = "high";
426                         break;
427 
428                     case GL_DEBUG_SEVERITY_MEDIUM:
429                         level = LogLevel.warning;
430                         sseverity = "medium";
431                         break;
432 
433                     case GL_DEBUG_SEVERITY_LOW:
434                         level = LogLevel.warning;
435                         sseverity = "low";
436                         break;
437 
438                     case GL_DEBUG_SEVERITY_NOTIFICATION:
439                         level = LogLevel.info;
440                         sseverity = "notification";
441                         break;
442 
443                     default:
444                         level = LogLevel.warning;
445                         sseverity = "unknown";
446                         break;
447                 }
448 
449                 const(char)[] text = fromStringz(message);
450 
451                 if (level == LogLevel.info)
452                     logger.infof("opengl: %s (id: %s, source: %s, type: %s, severity: %s)", text, id, ssource, stype, sseverity);
453                 if (level == LogLevel.warning)
454                     logger.warningf("opengl: %s (id: %s, source: %s, type: %s, severity: %s)", text, id, ssource, stype, sseverity);
455                 if (level == LogLevel.error)
456                     logger.errorf("opengl: %s (id: %s, source: %s, type: %s, severity: %s)", text, id, ssource, stype, sseverity);
457             }
458             catch (Exception e)
459             {
460                 // got exception while logging, ignore it
461             }
462         }
463         catch (Throwable e)
464         {
465             // No Throwable is supposed to cross C callbacks boundaries
466             // Crash immediately
467             exit(-1);
468         }
469     }
470 }
471 
472 /// Crash if the GC is running.
473 /// Useful in destructors to avoid reliance GC resource release.
474 package void ensureNotInGC(string resourceName) nothrow
475 {
476     import core.exception;
477     try
478     {
479         import core.memory;
480         cast(void) GC.malloc(1); // not ideal since it allocates
481         return;
482     }
483     catch(InvalidMemoryOperationError e)
484     {
485 
486         import core.stdc.stdio;
487         fprintf(stderr, "Error: clean-up of %s incorrectly depends on destructors called by the GC.\n", resourceName.ptr);
488         assert(false);
489     }
490 }