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