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