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.opengl3.gl3, 11 derelict.opengl3.gl; 12 13 import std.experimental.logger; 14 15 import gfm.core.log, 16 gfm.core.text, 17 gfm.opengl.textureunit; 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 DerelictGL3.load(); // load latest available version 57 58 DerelictGL.load(); // load deprecated functions too 59 60 _logger.infof("OpenGL loaded, version %s", DerelictGL3.loadedVersion()); 61 62 // do not log here since unimportant errors might happen: 63 // no context is necessarily created at this point 64 getLimits(false); 65 66 _textureUnits = new TextureUnits(this); 67 } 68 69 ~this() 70 { 71 close(); 72 } 73 74 /// Returns: true if the OpenGL extension is supported. 75 bool supportsExtension(string extension) 76 { 77 foreach(s; _extensions) 78 if (s == extension) 79 return true; 80 return false; 81 } 82 83 /// Reload OpenGL function pointers. 84 /// Once a first OpenGL context has been created, 85 /// you should call reload() to get the context you want. 86 void reload() 87 { 88 DerelictGL3.reload(); 89 _logger.infof("OpenGL reloaded, version %s", DerelictGL3.loadedVersion()); 90 _logger.infof(" Version: %s", getVersionString()); 91 _logger.infof(" Renderer: %s", getRendererString()); 92 _logger.infof(" Vendor: %s", getVendorString()); 93 _logger.infof(" GLSL version: %s", getGLSLVersionString()); 94 95 getLimits(true); 96 97 // Get available extensions 98 99 if (_majorVersion < 3) 100 { 101 // Legacy way to get extensions 102 _extensions = std.array.split(getString(GL_EXTENSIONS)); 103 } 104 else 105 { 106 // New way to get extensions 107 int numExtensions = getInteger(GL_NUM_EXTENSIONS, 0, true); 108 _extensions.length = 0; 109 for (int i = 0; i < numExtensions; ++i) 110 _extensions ~= getString(GL_EXTENSIONS, i); 111 } 112 113 _logger.infof(" Extensions: %s found", _extensions.length); 114 115 _textureUnits = new TextureUnits(this); 116 117 // now that the context exists, pipe OpenGL output 118 pipeOpenGLDebugOutput(); 119 } 120 121 /// Releases the OpenGL dynamic library. 122 /// All resources should have been released at this point, 123 /// since you won't be able to call any OpenGL function afterwards. 124 void close() 125 { 126 DerelictGL.unload(); 127 DerelictGL3.unload(); 128 } 129 130 /// Check for pending OpenGL errors, log a message if there is. 131 /// Only for debug purpose since this check will be disabled in a release build. 132 void debugCheck() 133 { 134 debug 135 { 136 GLint r = glGetError(); 137 if (r != GL_NO_ERROR) 138 { 139 flushGLErrors(); // flush other errors if any 140 _logger.errorf("OpenGL error: %s", getErrorString(r)); 141 assert(false); // break here 142 } 143 } 144 } 145 146 /// Checks pending OpenGL errors. 147 /// Throws: $(D OpenGLException) if at least one OpenGL error was pending. 148 void runtimeCheck() 149 { 150 GLint r = glGetError(); 151 if (r != GL_NO_ERROR) 152 { 153 string errorString = getErrorString(r); 154 flushGLErrors(); // flush other errors if any 155 throw new OpenGLException(errorString); 156 } 157 } 158 159 /// Checks pending OpenGL errors. 160 /// Returns: true if at least one OpenGL error was pending. OpenGL error status is cleared. 161 bool runtimeCheckNothrow() nothrow 162 { 163 GLint r = glGetError(); 164 if (r != GL_NO_ERROR) 165 { 166 flushGLErrors(); // flush other errors if any 167 return false; 168 } 169 return true; 170 } 171 172 /// Returns: OpenGL string returned by $(D glGetString). 173 /// See_also: $(WEB www.opengl.org/sdk/docs/man/xhtml/glGetString.xml) 174 string getString(GLenum name) 175 { 176 const(char)* sZ = glGetString(name); 177 runtimeCheck(); 178 if (sZ is null) 179 return "(unknown)"; 180 else 181 return sanitizeUTF8(sZ, _logger, "glGetString"); 182 } 183 184 /// Returns: OpenGL string returned by $(D glGetStringi) 185 /// See_also: $(WEB www.opengl.org/sdk.docs/man/xhtml/glGetString.xml) 186 string getString(GLenum name, GLuint index) 187 { 188 const(char)* sZ = glGetStringi(name, index); 189 runtimeCheck(); 190 if (sZ is null) 191 return "(unknown)"; 192 else 193 return sanitizeUTF8(sZ, _logger, "glGetStringi"); 194 } 195 196 /// Returns: OpenGL major version. 197 int getMajorVersion() pure const nothrow @nogc 198 { 199 return _majorVersion; 200 } 201 202 /// Returns: OpenGL minor version. 203 int getMinorVersion() pure const nothrow @nogc 204 { 205 return _minorVersion; 206 } 207 208 /// Returns: OpenGL version string, can be "major_number.minor_number" or 209 /// "major_number.minor_number.release_number". 210 string getVersionString() 211 { 212 return getString(GL_VERSION); 213 } 214 215 /// Returns: The company responsible for this OpenGL implementation, so 216 /// that you can plant a giant toxic mushroom below their office. 217 string getVendorString() 218 { 219 return getString(GL_VENDOR); 220 } 221 222 /// Tries to detect the driver maker. 223 /// Returns: Identified vendor. 224 Vendor getVendor() 225 { 226 string s = getVendorString(); 227 if (canFind(s, "AMD") || canFind(s, "ATI") || canFind(s, "Advanced Micro Devices")) 228 return Vendor.AMD; 229 else if (canFind(s, "NVIDIA") || canFind(s, "nouveau") || canFind(s, "Nouveau")) 230 return Vendor.NVIDIA; 231 else if (canFind(s, "Intel")) 232 return Vendor.Intel; 233 else if (canFind(s, "Mesa")) 234 return Vendor.Mesa; 235 else if (canFind(s, "Microsoft")) 236 return Vendor.Microsoft; 237 else if (canFind(s, "Apple")) 238 return Vendor.Apple; 239 else 240 return Vendor.other; 241 } 242 243 /// Returns: Name of the renderer. This name is typically specific 244 /// to a particular configuration of a hardware platform. 245 string getRendererString() 246 { 247 return getString(GL_RENDERER); 248 } 249 250 /// Returns: GLSL version string, can be "major_number.minor_number" or 251 /// "major_number.minor_number.release_number". 252 string getGLSLVersionString() 253 { 254 return getString(GL_SHADING_LANGUAGE_VERSION); 255 } 256 257 /// Returns: A huge space-separated list of OpenGL extensions. 258 deprecated("use getExtensions() instead") string getExtensionsString() 259 { 260 return std.array.join(_extensions, " "); 261 } 262 263 /// Returns: A slice made up of available extension names. 264 string[] getExtensions() pure nothrow @nogc 265 { 266 return _extensions; 267 } 268 269 /// Calls $(D glGetIntegerv) and gives back the requested integer. 270 /// Returns: true if $(D glGetIntegerv) succeeded. 271 /// See_also: $(WEB www.opengl.org/sdk/docs/man4/xhtml/glGet.xml). 272 /// Note: It is generally a bad idea to call $(D glGetSomething) since it might stall 273 /// the OpenGL pipeline. 274 bool getInteger(GLenum pname, out int result) nothrow 275 { 276 GLint param; 277 glGetIntegerv(pname, ¶m); 278 279 if (runtimeCheckNothrow()) 280 { 281 result = param; 282 return true; 283 } 284 else 285 return false; 286 } 287 288 /// Returns: The requested integer returned by $(D glGetIntegerv) 289 /// or defaultValue if an error occured. 290 /// See_also: $(WEB www.opengl.org/sdk/docs/man4/xhtml/glGet.xml). 291 int getInteger(GLenum pname, GLint defaultValue, bool logging) 292 { 293 int result; 294 295 if (getInteger(pname, result)) 296 { 297 return result; 298 } 299 else 300 { 301 if (logging) 302 _logger.warning("couldn't get OpenGL integer"); 303 return defaultValue; 304 } 305 } 306 307 /// Returns: The requested float returned by $(D glGetFloatv). 308 /// See_also: $(WEB www.opengl.org/sdk/docs/man4/xhtml/glGet.xml). 309 /// Throws: $(D OpenGLException) if at least one OpenGL error was pending. 310 float getFloat(GLenum pname) 311 { 312 GLfloat res; 313 glGetFloatv(pname, &res); 314 runtimeCheck(); 315 return res; 316 } 317 318 /// Returns: The requested float returned by $(D glGetFloatv) 319 /// or defaultValue if an error occured. 320 /// See_also: $(WEB www.opengl.org/sdk/docs/man4/xhtml/glGet.xml). 321 float getFloat(GLenum pname, GLfloat defaultValue, bool logging) 322 { 323 try 324 { 325 return getFloat(pname); 326 } 327 catch(OpenGLException e) 328 { 329 if (logging) 330 _logger.warning(e.msg); 331 return defaultValue; 332 } 333 } 334 } 335 336 package 337 { 338 Logger _logger; 339 340 static string getErrorString(GLint r) pure nothrow 341 { 342 switch(r) 343 { 344 case GL_NO_ERROR: return "GL_NO_ERROR"; 345 case GL_INVALID_ENUM: return "GL_INVALID_ENUM"; 346 case GL_INVALID_VALUE: return "GL_INVALID_VALUE"; 347 case GL_INVALID_OPERATION: return "GL_INVALID_OPERATION"; 348 case GL_OUT_OF_MEMORY: return "GL_OUT_OF_MEMORY"; 349 case GL_TABLE_TOO_LARGE: return "GL_TABLE_TOO_LARGE"; 350 case GL_STACK_OVERFLOW: return "GL_STACK_OVERFLOW"; 351 case GL_STACK_UNDERFLOW: return "GL_STACK_UNDERFLOW"; 352 default: return "Unknown OpenGL error"; 353 } 354 } 355 356 } 357 358 public 359 { 360 /// Returns: Maximum texture size. 361 /// This value should be at least 512 for a conforming OpenGL implementation. 362 int maxTextureSize() pure const nothrow 363 { 364 return _maxTextureSize; 365 } 366 367 /// Returns: Number of texture units. 368 int maxTextureUnits() pure const nothrow 369 { 370 return _maxTextureUnits; 371 } 372 373 /// Returns: Number of texture image units usable in a fragment shader. 374 int maxFragmentTextureImageUnits() pure const nothrow 375 { 376 return _maxFragmentTextureImageUnits; 377 } 378 379 /// Returns: Number of texture image units usable in a vertex shader. 380 int maxVertexImageUnits() pure const nothrow 381 { 382 return _maxVertexTextureImageUnits; 383 } 384 385 /// Returns: Number of combined texture image units. 386 int maxCombinedImageUnits() pure const nothrow 387 { 388 return _maxCombinedTextureImageUnits; 389 } 390 391 /// Returns: Maximum number of color attachments. This is the number of targets a fragment shader can output to. 392 /// You can rely on this number being at least 4 if MRT is supported. 393 int maxColorAttachments() pure const nothrow 394 { 395 return _maxColorAttachments; 396 } 397 398 /// Returns: Texture units abstraction. 399 TextureUnits textureUnits() pure nothrow 400 { 401 return _textureUnits; 402 } 403 404 /// Returns: Maximum value of anisotropic filter. 405 float maxTextureMaxAnisotropy() pure const nothrow 406 { 407 return _maxTextureMaxAnisotropy; 408 } 409 } 410 411 private 412 { 413 string[] _extensions; 414 TextureUnits _textureUnits; 415 int _majorVersion; 416 int _minorVersion; 417 int _maxTextureSize; 418 int _maxTextureUnits; // number of conventional units, deprecated 419 int _maxFragmentTextureImageUnits; // max for fragment shader 420 int _maxVertexTextureImageUnits; // max for vertex shader 421 int _maxCombinedTextureImageUnits; // max total 422 int _maxColorAttachments; 423 float _maxTextureMaxAnisotropy; 424 425 void getLimits(bool logging) 426 { 427 // parse GL_VERSION string 428 if (logging) 429 { 430 string verString = getVersionString(); 431 string[] verParts = std.array.split(verString, "."); 432 433 if (verParts.length < 2) 434 { 435 cant_parse: 436 _logger.warning(format("Couldn't parse GL_VERSION string '%s', assuming OpenGL 1.1", verString)); 437 _majorVersion = 1; 438 _minorVersion = 1; 439 } 440 else 441 { 442 try 443 _majorVersion = to!int(verParts[0]); 444 catch (Exception e) 445 goto cant_parse; 446 447 try 448 _minorVersion = to!int(verParts[1]); 449 catch (Exception e) 450 goto cant_parse; 451 } 452 } 453 else 454 { 455 _majorVersion = 1; 456 _minorVersion = 1; 457 } 458 459 _maxTextureSize = getInteger(GL_MAX_TEXTURE_SIZE, 512, logging); 460 // For other textures, add calls to: 461 // GL_MAX_ARRAY_TEXTURE_LAYERS, GL_MAX_3D_TEXTURE_SIZE 462 _maxTextureUnits = getInteger(GL_MAX_TEXTURE_UNITS, 2, logging); 463 464 _maxFragmentTextureImageUnits = getInteger(GL_MAX_TEXTURE_IMAGE_UNITS, 2, logging); // odd GL enum name because of legacy reasons (initially only fragment shader could access textures) 465 _maxVertexTextureImageUnits = getInteger(GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS, 2, logging); 466 _maxCombinedTextureImageUnits = getInteger(GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS, 2, logging); 467 // Get texture unit max for other shader stages with: 468 // GL_MAX_GEOMETRY_TEXTURE_IMAGE_UNITS, GL_MAX_TESS_CONTROL_TEXTURE_IMAGE_UNITS, GL_MAX_TESS_EVALUATION_TEXTURE_IMAGE_UNITS 469 470 _maxColorAttachments = getInteger(GL_MAX_COLOR_ATTACHMENTS, 4, logging); 471 472 if (EXT_texture_filter_anisotropic()) 473 _maxTextureMaxAnisotropy = getFloat(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, 1.0f, logging); 474 else 475 _maxTextureMaxAnisotropy = 1.0f; 476 } 477 478 // flush out OpenGL errors 479 void flushGLErrors() nothrow 480 { 481 int timeout = 0; 482 while (++timeout <= 5) // avoid infinite loop in a no-driver situation 483 { 484 GLint r = glGetError(); 485 if (r == GL_NO_ERROR) 486 break; 487 } 488 } 489 490 // Redirect OpenGL debug output to the provided Logger. 491 // You still has to use glDebugMessageControl to set which messages are emitted. 492 // For example, to enable all messages, use: 493 // glDebugMessageControl(GL_DONT_CARE, GL_DONT_CARE, GL_DONT_CARE, 0, null, GL_TRUE); 494 void pipeOpenGLDebugOutput() 495 { 496 if (KHR_debug()) 497 { 498 glDebugMessageCallback(&loggingCallbackOpenGL, cast(void*)this); 499 glEnable(GL_DEBUG_OUTPUT); 500 } 501 } 502 } 503 } 504 505 extern(System) private 506 { 507 // This callback can be called from multiple threads 508 nothrow void loggingCallbackOpenGL(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const(GLchar)* message, GLvoid* userParam) 509 { 510 try 511 { 512 OpenGL opengl = cast(OpenGL)userParam; 513 514 try 515 { 516 Logger logger = opengl._logger; 517 518 string ssource; 519 switch (source) 520 { 521 case GL_DEBUG_SOURCE_API: ssource = "API"; break; 522 case GL_DEBUG_SOURCE_WINDOW_SYSTEM: ssource = "window system"; break; 523 case GL_DEBUG_SOURCE_SHADER_COMPILER: ssource = "shader compiler"; break; 524 case GL_DEBUG_SOURCE_THIRD_PARTY: ssource = "third party"; break; 525 case GL_DEBUG_SOURCE_APPLICATION: ssource = "application"; break; 526 case GL_DEBUG_SOURCE_OTHER: ssource = "other"; break; 527 default: ssource= "unknown"; break; 528 } 529 530 string stype; 531 switch (type) 532 { 533 case GL_DEBUG_TYPE_ERROR: stype = "error"; break; 534 case GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR: stype = "deprecated behaviour"; break; 535 case GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR: stype = "undefined behaviour"; break; 536 case GL_DEBUG_TYPE_PORTABILITY: stype = "portabiliy"; break; 537 case GL_DEBUG_TYPE_PERFORMANCE: stype = "performance"; break; 538 case GL_DEBUG_TYPE_OTHER: stype = "other"; break; 539 default: stype = "unknown"; break; 540 } 541 542 LogLevel level; 543 544 string sseverity; 545 switch (severity) 546 { 547 case GL_DEBUG_SEVERITY_HIGH: 548 level = LogLevel.error; 549 sseverity = "high"; 550 break; 551 552 case GL_DEBUG_SEVERITY_MEDIUM: 553 level = LogLevel.warning; 554 sseverity = "medium"; 555 break; 556 557 case GL_DEBUG_SEVERITY_LOW: 558 level = LogLevel.warning; 559 sseverity = "low"; 560 break; 561 562 case GL_DEBUG_SEVERITY_NOTIFICATION: 563 level = LogLevel.info; 564 sseverity = "notification"; 565 break; 566 567 default: 568 level = LogLevel.warning; 569 sseverity = "unknown"; 570 break; 571 } 572 573 string text = sanitizeUTF8(message, logger, "OpenGL debug output"); 574 575 if (level == LogLevel.info) 576 logger.infof("opengl: %s (id: %s, source: %s, type: %s, severity: %s)", text, id, ssource, stype, sseverity); 577 if (level == LogLevel.warning) 578 logger.warningf("opengl: %s (id: %s, source: %s, type: %s, severity: %s)", text, id, ssource, stype, sseverity); 579 if (level == LogLevel.error) 580 logger.errorf("opengl: %s (id: %s, source: %s, type: %s, severity: %s)", text, id, ssource, stype, sseverity); 581 } 582 catch (Exception e) 583 { 584 // got exception while logging, ignore it 585 } 586 } 587 catch (Throwable e) 588 { 589 // No Throwable is supposed to cross C callbacks boundaries 590 // Crash immediately 591 exit(-1); 592 } 593 } 594 } 595