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