Skip to content

Commit

Permalink
feat: Using a glDebugMessageCallback instead of glGetError on devices…
Browse files Browse the repository at this point in the history
… that support it
  • Loading branch information
Chris Cameron committed Sep 2, 2019
1 parent 5d786f4 commit b07f2da
Show file tree
Hide file tree
Showing 2 changed files with 203 additions and 56 deletions.
1 change: 1 addition & 0 deletions AUTHORS
Expand Up @@ -42,6 +42,7 @@ Also thanks to:
* Bryan Wilbur
* Bugra Cuhadaroglu (BugraC)
* Christer Ulfsparre (Holloweye)
* Chris Cameron (Vesuvian)
* Chris Grant (Unit158)
* clem
* Cody Brittain (Generalcamo)
Expand Down
258 changes: 202 additions & 56 deletions OpenRA.Platforms.Default/OpenGL.cs
Expand Up @@ -10,6 +10,7 @@
#endregion

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.InteropServices;
Expand All @@ -24,17 +25,21 @@ namespace OpenRA.Platforms.Default
Justification = "C-style naming is kept for consistency with the underlying native API.")]
internal static class OpenGL
{
[Flags]
public enum GLFeatures
{
None = 0,
GL2OrGreater = 1,
FramebufferExt = 4,
DebugMessagesCallback = 8
}

public static GLFeatures Features { get; private set; }

public static string Version { get; private set; }

#region Constants

public const int GL_FALSE = 0;

// ClearBufferMask
Expand All @@ -48,7 +53,29 @@ public enum GLFeatures

// 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<int, string> ErrorToText = new Dictionary<int, string>
{
{ 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;
Expand Down Expand Up @@ -115,6 +142,63 @@ public enum GLFeatures
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<int, string> DebugSourceToText = new Dictionary<int, string>
{
{ 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<int, string> DebugTypeToText = new Dictionary<int, string>
{
{ 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<int, string> DebugSeverityToText = new Dictionary<int, string>
{
{ 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;
Expand All @@ -136,6 +220,20 @@ public enum GLFeatures
public const int DEPTH_ATTACHMENT_EXT = 0x8D00;
public const int FRAMEBUFFER_COMPLETE_EXT = 0x8CD5;

#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; }

Expand Down Expand Up @@ -356,28 +454,53 @@ public static string glGetString(int name)
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<Enable>("glEnable");
glDisable = Bind<Disable>("glDisable");
glGetError = Bind<GetError>("glGetError");
glGetStringInternal = Bind<GetString>("glGetString");
}
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.GL2OrGreater) || !Features.HasFlag(GLFeatures.FramebufferExt))
{
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<DebugMessageCallback>("glDebugMessageCallback");
glDebugMessageInsert = Bind<DebugMessageInsert>("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
{
Expand Down Expand Up @@ -419,8 +542,6 @@ public static void Initialize()
glEnableVertexAttribArray = Bind<EnableVertexAttribArray>("glEnableVertexAttribArray");
glDisableVertexAttribArray = Bind<DisableVertexAttribArray>("glDisableVertexAttribArray");
glDrawArrays = Bind<DrawArrays>("glDrawArrays");
glEnable = Bind<Enable>("glEnable");
glDisable = Bind<Disable>("glDisable");
glBlendEquation = Bind<BlendEquation>("glBlendEquation");
glBlendFunc = Bind<BlendFunc>("glBlendFunc");
glDepthFunc = Bind<DepthFunc>("glDepthFunc");
Expand Down Expand Up @@ -451,53 +572,7 @@ public static void Initialize()
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.");
}
}

static T Bind<T>(string name)
{
return (T)(object)Marshal.GetDelegateForFunctionPointer(SDL.SDL_GL_GetProcAddress(name), typeof(T));
}

public static void DetectGLFeatures()
{
try
{
Version = glGetString(GL_VERSION);
var version = Version.Contains(" ") ? Version.Split(' ')[0].Split('.') : Version.Split('.');

var major = 0;
if (version.Length > 0)
int.TryParse(version[0], out major);

var minor = 0;
if (version.Length > 1)
int.TryParse(version[1], out minor);

if (major >= 2 && minor >= 0)
Features |= GLFeatures.GL2OrGreater;

var hasFramebufferExt = SDL.SDL_GL_ExtensionSupported("GL_EXT_framebuffer_object") == SDL.SDL_bool.SDL_TRUE;
if (hasFramebufferExt)
Features |= GLFeatures.FramebufferExt;
}
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);
throw new InvalidProgramException("Failed to initialize OpenGL. See graphics.log for details.", e);
}
}

Expand All @@ -522,5 +597,76 @@ public static void WriteGraphicsLog(string message)
Log.Write("graphics", "Available extensions:");
Log.Write("graphics", glGetString(GL_EXTENSIONS));
}

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;

var errorText = ErrorToText[type];
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 T Bind<T>(string name)
{
return (T)(object)Marshal.GetDelegateForFunctionPointer(SDL.SDL_GL_GetProcAddress(name), typeof(T));
}

static void DetectGLFeatures()
{
Version = glGetString(GL_VERSION);
var version = Version.Contains(" ") ? Version.Split(' ')[0].Split('.') : Version.Split('.');

var major = 0;
if (version.Length > 0)
int.TryParse(version[0], out major);

var minor = 0;
if (version.Length > 1)
int.TryParse(version[1], out minor);

if (major >= 2 && minor >= 0)
Features |= GLFeatures.GL2OrGreater;

var hasFramebufferExt = SDL.SDL_GL_ExtensionSupported("GL_EXT_framebuffer_object") == SDL.SDL_bool.SDL_TRUE;
if (hasFramebufferExt)
Features |= GLFeatures.FramebufferExt;

var hasDebugMessagesCallback = SDL.SDL_GL_ExtensionSupported("GL_KHR_debug") == SDL.SDL_bool.SDL_TRUE;
if (hasDebugMessagesCallback)
Features |= GLFeatures.DebugMessagesCallback;
}

static void DebugMessageHandler(int source, int type, uint id, int severity, int length, StringBuilder message, IntPtr userparam)
{
if (severity != GL_DEBUG_SEVERITY_HIGH &&
severity != GL_DEBUG_SEVERITY_MEDIUM)
return;

string sourceText = DebugSourceToText[source];
string typeText = DebugTypeToText[type];
string severityText = DebugSeverityToText[severity];
string messageText = message.ToString();

var error = "{0} - GL Debug {1} Output: {2} - {3}\n{4}".F(severityText, sourceText, typeText, messageText, new StackTrace());

WriteGraphicsLog(error);

throw new InvalidOperationException("OpenGL Error: See graphics.log for details.");
}
}
}

0 comments on commit b07f2da

Please sign in to comment.