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