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