diff --git a/AUTHORS b/AUTHORS index fb98523ae3..1c8def7542 100644 --- a/AUTHORS +++ b/AUTHORS @@ -41,8 +41,9 @@ Also thanks to: * Brendan Gluth (Mechanical_Man) * Bryan Wilbur * Bugra Cuhadaroglu (BugraC) - * Christer Ulfsparre (Holloweye) + * Chris Cameron (Vesuvian) * Chris Grant (Unit158) + * Christer Ulfsparre (Holloweye) * clem * Cody Brittain (Generalcamo) * Constantin Helmig (CH4Code) diff --git a/OpenRA.Platforms.Default/OpenGL.cs b/OpenRA.Platforms.Default/OpenGL.cs index d74f05b2cc..f678015100 100644 --- a/OpenRA.Platforms.Default/OpenGL.cs +++ b/OpenRA.Platforms.Default/OpenGL.cs @@ -10,6 +10,7 @@ #endregion using System; +using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Runtime.InteropServices; @@ -30,12 +31,15 @@ namespace OpenRA.Platforms.Default None = 0, Core = 1, GLES = 2, + DebugMessagesCallback = 4 } public static GLFeatures Features { get; private set; } public static string Version { get; private set; } + #region Constants + public const int GL_FALSE = 0; // ClearBufferMask @@ -49,7 +53,29 @@ namespace OpenRA.Platforms.Default // Errors public const int GL_NO_ERROR = 0; - public const int GL_OUT_OF_MEMORY = 0x505; + public const int GL_INVALID_ENUM = 0x0500; + public const int GL_INVALID_VALUE = 0x0501; + public const int GL_INVALID_OPERATION = 0x0502; + public const int GL_STACK_OVERFLOW = 0x0503; + public const int GL_STACK_UNDERFLOW = 0x0504; + public const int GL_OUT_OF_MEMORY = 0x0505; + public const int GL_INVALID_FRAMEBUFFER_OPERATION = 0x0506; + public const int GL_CONTEXT_LOST = 0x0507; + public const int GL_TABLE_TOO_LARGE = 0x8031; + + static readonly Dictionary ErrorToText = new Dictionary + { + { GL_NO_ERROR, "No Error" }, + { GL_INVALID_ENUM, "Invalid Enum" }, + { GL_INVALID_VALUE, "Invalid Value" }, + { GL_INVALID_OPERATION, "Invalid Operation" }, + { GL_STACK_OVERFLOW, "Stack Overflow" }, + { GL_STACK_UNDERFLOW, "Stack Underflow" }, + { GL_OUT_OF_MEMORY, "Out Of Memory" }, + { GL_INVALID_FRAMEBUFFER_OPERATION, "Invalid Framebuffer Operation" }, + { GL_CONTEXT_LOST, "Context Lost" }, + { GL_TABLE_TOO_LARGE, "Table Too Large" }, + }; // BeginMode public const int GL_POINTS = 0; @@ -117,6 +143,63 @@ namespace OpenRA.Platforms.Default public const int GL_INFO_LOG_LENGTH = 0x8B84; public const int GL_ACTIVE_UNIFORMS = 0x8B86; + // OpenGL 4.3 + public const int GL_DEBUG_OUTPUT = 0x92E0; + public const int GL_DEBUG_OUTPUT_SYNCHRONOUS = 0x8242; + + public const int GL_DEBUG_SOURCE_API = 0x8246; + public const int GL_DEBUG_SOURCE_WINDOW_SYSTEM = 0x8247; + public const int GL_DEBUG_SOURCE_SHADER_COMPILER = 0x8248; + public const int GL_DEBUG_SOURCE_THIRD_PARTY = 0x8249; + public const int GL_DEBUG_SOURCE_APPLICATION = 0x824A; + public const int GL_DEBUG_SOURCE_OTHER = 0x824B; + + static readonly Dictionary DebugSourceToText = new Dictionary + { + { GL_DEBUG_SOURCE_API, "API" }, + { GL_DEBUG_SOURCE_WINDOW_SYSTEM, "Window System" }, + { GL_DEBUG_SOURCE_SHADER_COMPILER, "Shader Compiler" }, + { GL_DEBUG_SOURCE_THIRD_PARTY, "Third Party" }, + { GL_DEBUG_SOURCE_APPLICATION, "Application" }, + { GL_DEBUG_SOURCE_OTHER, "Other" } + }; + + public const int GL_DEBUG_TYPE_ERROR = 0x824C; + public const int GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR = 0x824D; + public const int GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR = 0x824E; + public const int GL_DEBUG_TYPE_PORTABILITY = 0x824F; + public const int GL_DEBUG_TYPE_PERFORMANCE = 0x8250; + public const int GL_DEBUG_TYPE_MARKER = 0x8268; + public const int GL_DEBUG_TYPE_PUSH_GROUP = 0x8269; + public const int GL_DEBUG_TYPE_POP_GROUP = 0x826A; + public const int GL_DEBUG_TYPE_OTHER = 0x8251; + + static readonly Dictionary DebugTypeToText = new Dictionary + { + { GL_DEBUG_TYPE_ERROR, "Error" }, + { GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR, "Deprecated Behaviour" }, + { GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR, "Undefined Behaviour" }, + { GL_DEBUG_TYPE_PORTABILITY, "Portability" }, + { GL_DEBUG_TYPE_PERFORMANCE, "Performance" }, + { GL_DEBUG_TYPE_MARKER, "Marker" }, + { GL_DEBUG_TYPE_PUSH_GROUP, "Push Group" }, + { GL_DEBUG_TYPE_POP_GROUP, "Pop Group" }, + { GL_DEBUG_TYPE_OTHER, "Other" } + }; + + public const int GL_DEBUG_SEVERITY_HIGH = 0x9146; + public const int GL_DEBUG_SEVERITY_MEDIUM = 0x9147; + public const int GL_DEBUG_SEVERITY_LOW = 0x9148; + public const int GL_DEBUG_SEVERITY_NOTIFICATION = 0x826B; + + static readonly Dictionary DebugSeverityToText = new Dictionary + { + { GL_DEBUG_SEVERITY_HIGH, "High" }, + { GL_DEBUG_SEVERITY_MEDIUM, "Medium" }, + { GL_DEBUG_SEVERITY_LOW, "Low" }, + { GL_DEBUG_SEVERITY_NOTIFICATION, "Notification" } + }; + // Pixel Mode / Transfer public const int GL_PACK_ROW_LENGTH = 0x0D02; public const int GL_PACK_ALIGNMENT = 0x0D05; @@ -141,6 +224,20 @@ namespace OpenRA.Platforms.Default public const int GL_FRAMEBUFFER_COMPLETE = 0x8CD5; public const int GL_FRAMEBUFFER_BINDING = 0x8CA6; + #endregion + + #region GL Delegates + + public delegate void DebugProc(int source, int type, uint id, int severity, int length, StringBuilder message, + IntPtr userParam); + static DebugProc DebugMessageHandle { get; set; } + + public delegate void DebugMessageCallback(DebugProc callback, IntPtr userParam); + public static DebugMessageCallback glDebugMessageCallback { get; private set; } + + public delegate void DebugMessageInsert(int source, int type, uint id, int severity, int length, string message); + public static DebugMessageInsert glDebugMessageInsert { get; private set; } + public delegate void Flush(); public static Flush glFlush { get; private set; } @@ -378,29 +475,54 @@ namespace OpenRA.Platforms.Default public delegate int CheckFramebufferStatus(int target); public static CheckFramebufferStatus glCheckFramebufferStatus { get; private set; } + #endregion + public static void Initialize() { - // glGetError and glGetString are used in our error handlers - // so we want these to be available early. try { + // First set up the bindings we need for error handling + glEnable = Bind("glEnable"); + glDisable = Bind("glDisable"); glGetError = Bind("glGetError"); glGetStringInternal = Bind("glGetString"); glGetStringiInternal = Bind("glGetStringi"); } - catch (Exception) + catch (Exception e) { - throw new InvalidProgramException("Failed to initialize low-level OpenGL bindings. GPU information is not available"); + throw new InvalidProgramException("Failed to initialize low-level OpenGL bindings. GPU information is not available.", e); } DetectGLFeatures(); + if (!Features.HasFlag(GLFeatures.Core)) { WriteGraphicsLog("Unsupported OpenGL version: " + glGetString(GL_VERSION)); throw new InvalidProgramException("OpenGL Version Error: See graphics.log for details."); } - else - Console.WriteLine("OpenGL version: " + glGetString(GL_VERSION)); + + // Setup the debug message callback handler + if (Features.HasFlag(GLFeatures.DebugMessagesCallback)) + { + try + { + glDebugMessageCallback = Bind("glDebugMessageCallback"); + glDebugMessageInsert = Bind("glDebugMessageInsert"); + + glEnable(GL_DEBUG_OUTPUT); + glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS); + + // Need to keep a reference to the callback so it doesn't get garbage collected + DebugMessageHandle = DebugMessageHandler; + glDebugMessageCallback(DebugMessageHandle, IntPtr.Zero); + } + catch (Exception e) + { + throw new InvalidProgramException("Failed to initialize an OpenGL debug message callback.", e); + } + } + + Console.WriteLine("OpenGL version: " + glGetString(GL_VERSION)); try { @@ -445,8 +567,6 @@ namespace OpenRA.Platforms.Default glEnableVertexAttribArray = Bind("glEnableVertexAttribArray"); glDisableVertexAttribArray = Bind("glDisableVertexAttribArray"); glDrawArrays = Bind("glDrawArrays"); - glEnable = Bind("glEnable"); - glDisable = Bind("glDisable"); glBlendEquation = Bind("glBlendEquation"); glBlendEquationSeparate = Bind("glBlendEquationSeparate"); glBlendFunc = Bind("glBlendFunc"); @@ -476,7 +596,7 @@ namespace OpenRA.Platforms.Default catch (Exception e) { WriteGraphicsLog("Failed to initialize OpenGL bindings.\nInner exception was: {0}".F(e)); - throw new InvalidProgramException("Failed to initialize OpenGL. See graphics.log for details."); + throw new InvalidProgramException("Failed to initialize OpenGL. See graphics.log for details.", e); } } @@ -508,26 +628,15 @@ namespace OpenRA.Platforms.Default Features = GLFeatures.Core | GLFeatures.GLES; else if (major > 3 || (major == 3 && minor >= 2)) Features = GLFeatures.Core; + + // Debug callbacks were introduced in GL 4.3 + var hasDebugMessagesCallback = SDL.SDL_GL_ExtensionSupported("GL_KHR_debug") == SDL.SDL_bool.SDL_TRUE; + if (hasDebugMessagesCallback) + Features |= GLFeatures.DebugMessagesCallback; } catch (Exception) { } } - public static void CheckGLError() - { - var n = glGetError(); - if (n != GL_NO_ERROR) - { - var errorText = n == GL_OUT_OF_MEMORY ? "Out Of Memory" : n.ToString(); - var error = "GL Error: {0}\n{1}".F(errorText, new StackTrace()); - WriteGraphicsLog(error); - const string ExceptionMessage = "OpenGL Error: See graphics.log for details."; - if (n == GL_OUT_OF_MEMORY) - throw new OutOfMemoryException(ExceptionMessage); - else - throw new InvalidOperationException(ExceptionMessage); - } - } - public static void WriteGraphicsLog(string message) { Log.Write("graphics", message); @@ -553,5 +662,61 @@ namespace OpenRA.Platforms.Default for (var i = 0; i < extensionCount; i++) Log.Write("graphics", glGetStringi(GL_EXTENSIONS, (uint)i)); } + + public static void CheckGLError() + { + // Let the debug message handler log the errors instead. + if (Features.HasFlag(GLFeatures.DebugMessagesCallback)) + return; + + var type = glGetError(); + if (type == GL_NO_ERROR) + return; + + string errorText; + errorText = ErrorToText.TryGetValue(type, out errorText) ? errorText : type.ToString("X"); + var error = "GL Error: {0}\n{1}".F(errorText, new StackTrace()); + + WriteGraphicsLog(error); + + const string exceptionMessage = "OpenGL Error: See graphics.log for details."; + + if (type == GL_OUT_OF_MEMORY) + throw new OutOfMemoryException(exceptionMessage); + + throw new InvalidOperationException(exceptionMessage); + } + + static void DebugMessageHandler(int source, int type, uint id, int severity, int length, StringBuilder message, IntPtr userparam) + { + string error; + + switch (severity) + { + case GL_DEBUG_SEVERITY_HIGH: + error = BuildErrorText(source, type, severity, message); + WriteGraphicsLog(error); + throw new InvalidOperationException("OpenGL Error: See graphics.log for details."); + + case GL_DEBUG_SEVERITY_MEDIUM: + error = BuildErrorText(source, type, severity, message); + Console.WriteLine(error); + break; + } + } + + static string BuildErrorText(int source, int type, int severity, StringBuilder message) + { + string sourceText; + string typeText; + string severityText; + + sourceText = DebugSourceToText.TryGetValue(source, out sourceText) ? sourceText : source.ToString("X"); + typeText = DebugTypeToText.TryGetValue(type, out typeText) ? typeText : type.ToString("X"); + severityText = DebugSeverityToText.TryGetValue(severity, out severityText) ? severityText : severity.ToString("X"); + var messageText = message.ToString(); + + return "{0} - GL Debug {1} Output: {2} - {3}\n{4}".F(severityText, sourceText, typeText, messageText, new StackTrace()); + } } }