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