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, ¶m); 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 }