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