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