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