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 import gfm.core.log,
16        gfm.core.text,
17        gfm.opengl.textureunit;
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             DerelictGL3.load(); // load latest available version
57 
58             DerelictGL.load(); // load deprecated functions too
59 
60             _logger.infof("OpenGL loaded, version %s", DerelictGL3.loadedVersion());
61 
62             // do not log here since unimportant errors might happen:
63             // no context is necessarily created at this point
64             getLimits(false); 
65 
66             _textureUnits = new TextureUnits(this);
67         }
68 
69         ~this()
70         {
71             close();
72         }
73 
74         /// Returns: true if the OpenGL extension is supported.
75         bool supportsExtension(string extension)
76         {
77             foreach(s; _extensions)
78                 if (s == extension)
79                     return true;
80             return false;
81         }
82 
83         /// Reload OpenGL function pointers.
84         /// Once a first OpenGL context has been created, 
85         /// you should call reload() to get the context you want.
86         void reload()
87         {
88             DerelictGL3.reload();
89             _logger.infof("OpenGL reloaded, version %s", DerelictGL3.loadedVersion());
90             _logger.infof("    Version: %s", getVersionString());
91             _logger.infof("    Renderer: %s", getRendererString());
92             _logger.infof("    Vendor: %s", getVendorString());
93             _logger.infof("    GLSL version: %s", getGLSLVersionString());
94 
95             getLimits(true);
96 
97             // Get available extensions
98 
99             if (_majorVersion < 3)
100             {
101                 // Legacy way to get extensions
102                 _extensions = std.array.split(getString(GL_EXTENSIONS));
103             }
104             else
105             {
106                 // New way to get extensions
107                 int numExtensions = getInteger(GL_NUM_EXTENSIONS, 0, true);
108                 _extensions.length = 0;
109                 for (int i = 0; i < numExtensions; ++i)
110                     _extensions ~= getString(GL_EXTENSIONS, i);
111             }
112 
113             _logger.infof("    Extensions: %s found", _extensions.length);
114 
115             _textureUnits = new TextureUnits(this);
116 
117             // now that the context exists, pipe OpenGL output
118             pipeOpenGLDebugOutput();
119         }
120 
121         /// Releases the OpenGL dynamic library.
122         /// All resources should have been released at this point,
123         /// since you won't be able to call any OpenGL function afterwards.
124         void close()
125         {
126             DerelictGL.unload();
127             DerelictGL3.unload();
128         }
129 
130         /// Check for pending OpenGL errors, log a message if there is.
131         /// Only for debug purpose since this check will be disabled in a release build.
132         void debugCheck()
133         {
134             debug
135             {
136                 GLint r = glGetError();
137                 if (r != GL_NO_ERROR)
138                 {
139                     flushGLErrors(); // flush other errors if any
140                     _logger.errorf("OpenGL error: %s", getErrorString(r));
141                     assert(false); // break here
142                 }
143             }
144         }
145 
146         /// Checks pending OpenGL errors.
147         /// Throws: $(D OpenGLException) if at least one OpenGL error was pending.
148         void runtimeCheck()
149         {
150             GLint r = glGetError();
151             if (r != GL_NO_ERROR)
152             {
153                 string errorString = getErrorString(r);
154                 flushGLErrors(); // flush other errors if any
155                 throw new OpenGLException(errorString);
156             }
157         }
158 
159         /// Checks pending OpenGL errors.
160         /// Returns: true if at least one OpenGL error was pending. OpenGL error status is cleared.
161         bool runtimeCheckNothrow() nothrow
162         {
163             GLint r = glGetError();
164             if (r != GL_NO_ERROR)
165             {
166                 flushGLErrors(); // flush other errors if any
167                 return false;
168             }
169             return true;
170         }
171 
172         /// Returns: OpenGL string returned by $(D glGetString).
173         /// See_also: $(WEB www.opengl.org/sdk/docs/man/xhtml/glGetString.xml)
174         string getString(GLenum name)
175         {
176             const(char)* sZ = glGetString(name);
177             runtimeCheck();
178             if (sZ is null)
179                 return "(unknown)";
180             else
181                 return sanitizeUTF8(sZ, _logger, "glGetString");
182         }
183 
184         /// Returns: OpenGL string returned by $(D glGetStringi)
185         /// See_also: $(WEB www.opengl.org/sdk.docs/man/xhtml/glGetString.xml)
186         string getString(GLenum name, GLuint index)
187         {
188             const(char)* sZ = glGetStringi(name, index);
189             runtimeCheck();
190             if (sZ is null)
191                 return "(unknown)";
192             else
193                 return sanitizeUTF8(sZ, _logger, "glGetStringi");
194         }
195 
196         /// Returns: OpenGL major version.
197         int getMajorVersion() pure const nothrow @nogc
198         {
199             return _majorVersion;
200         }
201 
202         /// Returns: OpenGL minor version.
203         int getMinorVersion() pure const nothrow @nogc
204         {
205             return _minorVersion;
206         }
207 
208         /// Returns: OpenGL version string, can be "major_number.minor_number" or 
209         ///          "major_number.minor_number.release_number".
210         string getVersionString()
211         {
212             return getString(GL_VERSION);
213         }
214 
215         /// Returns: The company responsible for this OpenGL implementation, so
216         ///          that you can plant a giant toxic mushroom below their office.
217         string getVendorString()
218         {
219             return getString(GL_VENDOR);
220         }
221 
222         /// Tries to detect the driver maker.
223         /// Returns: Identified vendor.
224         Vendor getVendor()
225         {
226             string s = getVendorString();
227             if (canFind(s, "AMD") || canFind(s, "ATI") || canFind(s, "Advanced Micro Devices"))
228                 return Vendor.AMD;
229             else if (canFind(s, "NVIDIA") || canFind(s, "nouveau") || canFind(s, "Nouveau"))
230                 return Vendor.NVIDIA;
231             else if (canFind(s, "Intel"))
232                 return Vendor.Intel;
233             else if (canFind(s, "Mesa"))
234                 return Vendor.Mesa;
235             else if (canFind(s, "Microsoft"))
236                 return Vendor.Microsoft;
237             else if (canFind(s, "Apple"))
238                 return Vendor.Apple;
239             else
240                 return Vendor.other;
241         }
242 
243         /// Returns: Name of the renderer. This name is typically specific 
244         ///          to a particular configuration of a hardware platform.
245         string getRendererString()
246         {
247             return getString(GL_RENDERER);
248         }
249 
250         /// Returns: GLSL version string, can be "major_number.minor_number" or 
251         ///          "major_number.minor_number.release_number".
252         string getGLSLVersionString()
253         {
254             return getString(GL_SHADING_LANGUAGE_VERSION);
255         }
256 
257         /// Returns: A huge space-separated list of OpenGL extensions.
258         deprecated("use getExtensions() instead") string getExtensionsString()
259         {
260             return std.array.join(_extensions, " ");
261         }
262 
263         /// Returns: A slice made up of available extension names.
264         string[] getExtensions() pure nothrow @nogc
265         {
266             return _extensions;
267         }
268 
269         /// Calls $(D glGetIntegerv) and gives back the requested integer.
270         /// Returns: true if $(D glGetIntegerv) succeeded.
271         /// See_also: $(WEB www.opengl.org/sdk/docs/man4/xhtml/glGet.xml).
272         /// Note: It is generally a bad idea to call $(D glGetSomething) since it might stall
273         ///       the OpenGL pipeline.
274         bool getInteger(GLenum pname, out int result) nothrow
275         {
276             GLint param;
277             glGetIntegerv(pname, &param);
278 
279             if (runtimeCheckNothrow())
280             {
281                 result = param;
282                 return true;
283             }
284             else
285                 return false;
286         }
287 
288         /// Returns: The requested integer returned by $(D glGetIntegerv) 
289         ///          or defaultValue if an error occured.
290         /// See_also: $(WEB www.opengl.org/sdk/docs/man4/xhtml/glGet.xml).        
291         int getInteger(GLenum pname, GLint defaultValue, bool logging)
292         {
293             int result;
294 
295             if (getInteger(pname, result))
296             {
297                 return result;
298             }
299             else
300             {
301                 if (logging)
302                     _logger.warning("couldn't get OpenGL integer");
303                 return defaultValue;
304             }
305         }
306 
307         /// Returns: The requested float returned by $(D glGetFloatv).
308         /// See_also: $(WEB www.opengl.org/sdk/docs/man4/xhtml/glGet.xml). 
309         /// Throws: $(D OpenGLException) if at least one OpenGL error was pending.
310         float getFloat(GLenum pname)
311         {
312             GLfloat res;
313             glGetFloatv(pname, &res);
314             runtimeCheck();
315             return res;
316         }
317 
318         /// Returns: The requested float returned by $(D glGetFloatv) 
319         ///          or defaultValue if an error occured.
320         /// See_also: $(WEB www.opengl.org/sdk/docs/man4/xhtml/glGet.xml).   
321         float getFloat(GLenum pname, GLfloat defaultValue, bool logging)
322         {
323             try
324             {
325                 return getFloat(pname);
326             }
327             catch(OpenGLException e)
328             {
329                 if (logging)
330                     _logger.warning(e.msg);
331                 return defaultValue;
332             }
333         }
334     }
335 
336     package
337     {
338         Logger _logger;
339 
340         static string getErrorString(GLint r) pure nothrow
341         {
342             switch(r)
343             {
344                 case GL_NO_ERROR:          return "GL_NO_ERROR";
345                 case GL_INVALID_ENUM:      return "GL_INVALID_ENUM";
346                 case GL_INVALID_VALUE:     return "GL_INVALID_VALUE";
347                 case GL_INVALID_OPERATION: return "GL_INVALID_OPERATION";
348                 case GL_OUT_OF_MEMORY:     return "GL_OUT_OF_MEMORY";
349                 case GL_TABLE_TOO_LARGE:   return "GL_TABLE_TOO_LARGE";
350                 case GL_STACK_OVERFLOW:    return "GL_STACK_OVERFLOW";
351                 case GL_STACK_UNDERFLOW:   return "GL_STACK_UNDERFLOW";
352                 default:                   return "Unknown OpenGL error";
353             }
354         }
355 
356     }
357 
358     public
359     {
360         /// Returns: Maximum texture size. 
361         ///          This value should be at least 512 for a conforming OpenGL implementation.
362         int maxTextureSize() pure const nothrow
363         {
364             return _maxTextureSize;
365         }
366 
367         /// Returns: Number of texture units.
368         int maxTextureUnits() pure const nothrow
369         {
370             return _maxTextureUnits;
371         }
372 
373         /// Returns: Number of texture image units usable in a fragment shader.
374         int maxFragmentTextureImageUnits() pure const nothrow
375         {
376             return _maxFragmentTextureImageUnits;
377         }
378 
379         /// Returns: Number of texture image units usable in a vertex shader.
380         int maxVertexImageUnits() pure const nothrow
381         {
382             return _maxVertexTextureImageUnits;
383         }
384 
385         /// Returns: Number of combined texture image units.
386         int maxCombinedImageUnits() pure const nothrow
387         {
388             return _maxCombinedTextureImageUnits;
389         }
390 
391         /// Returns: Maximum number of color attachments. This is the number of targets a fragment shader can output to.
392         /// You can rely on this number being at least 4 if MRT is supported.
393         int maxColorAttachments() pure const nothrow
394         {
395             return _maxColorAttachments;
396         }
397 
398         /// Returns: Texture units abstraction.
399         TextureUnits textureUnits() pure nothrow
400         {
401             return _textureUnits;
402         }
403 
404         /// Returns: Maximum value of anisotropic filter.
405         float maxTextureMaxAnisotropy() pure const nothrow
406         {
407             return _maxTextureMaxAnisotropy;
408         }
409     }
410 
411     private
412     {
413         string[] _extensions;
414         TextureUnits _textureUnits;
415         int _majorVersion;
416         int _minorVersion;
417         int _maxTextureSize;
418         int _maxTextureUnits; // number of conventional units, deprecated
419         int _maxFragmentTextureImageUnits; // max for fragment shader
420         int _maxVertexTextureImageUnits; // max for vertex shader
421         int _maxCombinedTextureImageUnits; // max total
422         int _maxColorAttachments;
423         float _maxTextureMaxAnisotropy;
424 
425         void getLimits(bool logging)
426         {
427             // parse GL_VERSION string
428             if (logging)
429             {
430                 string verString = getVersionString();
431                 string[] verParts = std.array.split(verString, ".");
432 
433                 if (verParts.length < 2)
434                 {
435                     cant_parse:
436                     _logger.warning(format("Couldn't parse GL_VERSION string '%s', assuming OpenGL 1.1", verString));
437                     _majorVersion = 1;
438                     _minorVersion = 1;
439                 }
440                 else
441                 {
442                     try
443                         _majorVersion = to!int(verParts[0]);
444                     catch (Exception e)
445                         goto cant_parse;
446                 
447                     try
448                         _minorVersion = to!int(verParts[1]);
449                     catch (Exception e)
450                         goto cant_parse;
451                 }
452             }
453             else
454             {
455                 _majorVersion = 1;
456                 _minorVersion = 1;
457             }
458 
459             _maxTextureSize = getInteger(GL_MAX_TEXTURE_SIZE, 512, logging);
460             // For other textures, add calls to:
461             // GL_MAX_ARRAY_TEXTURE_LAYERS​, GL_MAX_3D_TEXTURE_SIZE​
462             _maxTextureUnits = getInteger(GL_MAX_TEXTURE_UNITS, 2, logging);
463 
464             _maxFragmentTextureImageUnits = getInteger(GL_MAX_TEXTURE_IMAGE_UNITS, 2, logging); // odd GL enum name because of legacy reasons (initially only fragment shader could access textures)
465             _maxVertexTextureImageUnits = getInteger(GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS, 2, logging);
466             _maxCombinedTextureImageUnits = getInteger(GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS, 2, logging);
467             // Get texture unit max for other shader stages with:
468             // GL_MAX_GEOMETRY_TEXTURE_IMAGE_UNITS, GL_MAX_TESS_CONTROL_TEXTURE_IMAGE_UNITS, GL_MAX_TESS_EVALUATION_TEXTURE_IMAGE_UNITS
469 
470             _maxColorAttachments = getInteger(GL_MAX_COLOR_ATTACHMENTS, 4, logging);
471 
472             if (EXT_texture_filter_anisotropic())
473                 _maxTextureMaxAnisotropy = getFloat(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, 1.0f, logging);
474             else
475                 _maxTextureMaxAnisotropy = 1.0f;
476         }
477 
478         // flush out OpenGL errors
479         void flushGLErrors() nothrow
480         {            
481             int timeout = 0;
482             while (++timeout <= 5) // avoid infinite loop in a no-driver situation
483             {
484                 GLint r = glGetError();
485                 if (r == GL_NO_ERROR)
486                     break;
487             }
488         }
489 
490         // Redirect OpenGL debug output to the provided Logger.
491         // You still has to use glDebugMessageControl to set which messages are emitted.
492         // For example, to enable all messages, use:
493         // glDebugMessageControl(GL_DONT_CARE, GL_DONT_CARE, GL_DONT_CARE, 0, null, GL_TRUE);
494         void pipeOpenGLDebugOutput()
495         {
496             if (KHR_debug())
497             {
498                 glDebugMessageCallback(&loggingCallbackOpenGL, cast(void*)this);
499                 glEnable(GL_DEBUG_OUTPUT);
500             }
501         }
502     }
503 }
504 
505 extern(System) private
506 {
507     // This callback can be called from multiple threads
508     nothrow void loggingCallbackOpenGL(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const(GLchar)* message, GLvoid* userParam)
509     {
510         try
511         {
512             OpenGL opengl = cast(OpenGL)userParam;
513 
514             try
515             {
516                 Logger logger = opengl._logger;
517 
518                 string ssource;
519                 switch (source)
520                 {
521                     case GL_DEBUG_SOURCE_API:             ssource = "API"; break;
522                     case GL_DEBUG_SOURCE_WINDOW_SYSTEM:   ssource = "window system"; break;
523                     case GL_DEBUG_SOURCE_SHADER_COMPILER: ssource = "shader compiler"; break;
524                     case GL_DEBUG_SOURCE_THIRD_PARTY:     ssource = "third party"; break;
525                     case GL_DEBUG_SOURCE_APPLICATION:     ssource = "application"; break;
526                     case GL_DEBUG_SOURCE_OTHER:           ssource = "other"; break;
527                     default:                              ssource= "unknown"; break;
528                 }
529 
530                 string stype;
531                 switch (type)
532                 {
533                     case GL_DEBUG_TYPE_ERROR:               stype = "error"; break;
534                     case GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR: stype = "deprecated behaviour"; break;
535                     case GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR:  stype = "undefined behaviour"; break;
536                     case GL_DEBUG_TYPE_PORTABILITY:         stype = "portabiliy"; break;
537                     case GL_DEBUG_TYPE_PERFORMANCE:         stype = "performance"; break;
538                     case GL_DEBUG_TYPE_OTHER:               stype = "other"; break;
539                     default:                                stype = "unknown"; break;
540                 }
541 
542                 LogLevel level;
543 
544                 string sseverity;
545                 switch (severity)
546                 {
547                     case GL_DEBUG_SEVERITY_HIGH:
548                         level = LogLevel.error;
549                         sseverity = "high";
550                         break;
551 
552                     case GL_DEBUG_SEVERITY_MEDIUM: 
553                         level = LogLevel.warning;
554                         sseverity = "medium";
555                         break;
556 
557                     case GL_DEBUG_SEVERITY_LOW:    
558                         level = LogLevel.warning;
559                         sseverity = "low";
560                         break;
561 
562                     case GL_DEBUG_SEVERITY_NOTIFICATION:
563                         level = LogLevel.info;
564                         sseverity = "notification";
565                         break;
566 
567                     default:
568                         level = LogLevel.warning;
569                         sseverity = "unknown";
570                         break;
571                 }
572 
573                 string text = sanitizeUTF8(message, logger, "OpenGL debug output");
574 
575                 if (level == LogLevel.info)
576                     logger.infof("opengl: %s (id: %s, source: %s, type: %s, severity: %s)", text, id, ssource, stype, sseverity);
577                 if (level == LogLevel.warning)
578                     logger.warningf("opengl: %s (id: %s, source: %s, type: %s, severity: %s)", text, id, ssource, stype, sseverity);
579                 if (level == LogLevel.error)
580                     logger.errorf("opengl: %s (id: %s, source: %s, type: %s, severity: %s)", text, id, ssource, stype, sseverity);
581             }
582             catch (Exception e)
583             {
584                 // got exception while logging, ignore it
585             }
586         }
587         catch (Throwable e)
588         {
589             // No Throwable is supposed to cross C callbacks boundaries
590             // Crash immediately
591             exit(-1);
592         }
593     }
594 }
595