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