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