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