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