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