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