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