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