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