Skip to content

Commit

Permalink
Cleaned up temporary context construction
Browse files Browse the repository at this point in the history
The temporary context is now retained until the actual context has been
constructed. If we don't do this, then WGL_ARB_create_context may fail
to work correctly on specific GPUs (e.g. Intel). This may affect issue
#19.
  • Loading branch information
thefiddler committed Dec 18, 2013
1 parent a57b4c4 commit d8a4ca1
Showing 1 changed file with 123 additions and 88 deletions.
211 changes: 123 additions & 88 deletions Source/OpenTK/Platform/Windows/WinGLContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,88 +28,111 @@ namespace OpenTK.Platform.Windows
internal sealed class WinGLContext : DesktopGraphicsContext
{
static readonly object LoadLock = new object();
static readonly object SyncRoot = new object();

bool vsync_supported;

readonly WinGraphicsMode ModeSelector;

#region --- Contructors ---

static WinGLContext()
// We need to create a temp context in order to load
// wgl extensions (e.g. for multisampling or GL3).
// We cannot rely on any WGL extensions before
// we load them with the temporary context.
class TemporaryContext : IDisposable
{
lock (LoadLock)
public ContextHandle Context;

public TemporaryContext(INativeWindow native)
{
// We need to create a temp context in order to load
// wgl extensions (e.g. for multisampling or GL3).
// We cannot rely on OpenTK.Platform.Wgl until we
// create the context and call Wgl.LoadAll().
Debug.Print("Creating temporary context for wgl extensions.");
using (INativeWindow native = new NativeWindow())
Debug.WriteLine("[WGL] Creating temporary context to load extensions");

if (native == null)
throw new ArgumentNullException();

// Create temporary context and load WGL entry points
// First, set a compatible pixel format to the device context
// of the temp window
WinWindowInfo window = native.WindowInfo as WinWindowInfo;
WinGraphicsMode selector = new WinGraphicsMode(window.DeviceContext);
WinGLContext.SetGraphicsModePFD(selector, GraphicsMode.Default, window);

bool success = false;

// Then, construct a temporary context and load all wgl extensions
Context = new ContextHandle(Wgl.CreateContext(window.DeviceContext));
if (Context != ContextHandle.Zero)
{
// Create temporary context and load WGL entry points
// First, set a compatible pixel format to the device context
// of the temp window
WinWindowInfo window = native.WindowInfo as WinWindowInfo;
WinGraphicsMode selector = new WinGraphicsMode(window.DeviceContext);
SetGraphicsModePFD(selector, GraphicsMode.Default, window);

// Then, construct a temporary context and load all wgl extensions
ContextHandle temp_context = new ContextHandle(Wgl.CreateContext(window.DeviceContext));
if (temp_context != ContextHandle.Zero)
{
// Make the context current.
// Note: on some video cards and on some virtual machines, wglMakeCurrent
// may fail with an errorcode of 6 (INVALID_HANDLE). The suggested workaround
// is to call wglMakeCurrent in a loop until it succeeds.
// See https://www.opengl.org/discussion_boards/showthread.php/171058-nVidia-wglMakeCurrent()-multiple-threads
// Sigh...
for (int retry = 0; retry < 5; retry++)
{
bool success = Wgl.MakeCurrent(window.DeviceContext, temp_context.Handle);
if (!success)
{
Debug.Print("wglMakeCurrent failed with error: {0}. Retrying", Marshal.GetLastWin32Error());
System.Threading.Thread.Sleep(10);
}
else
{
// wglMakeCurrent succeeded, we are done here!
break;
}
}

// Load wgl extensions and destroy temporary context
Wgl.LoadAll();
Wgl.MakeCurrent(IntPtr.Zero, IntPtr.Zero);
Wgl.DeleteContext(temp_context.Handle);
}
else
// Make the context current.
// Note: on some video cards and on some virtual machines, wglMakeCurrent
// may fail with an errorcode of 6 (INVALID_HANDLE). The suggested workaround
// is to call wglMakeCurrent in a loop until it succeeds.
// See https://www.opengl.org/discussion_boards/showthread.php/171058-nVidia-wglMakeCurrent()-multiple-threads
// Sigh...
for (int retry = 0; retry < 5 && !success; retry++)
{
Debug.Print("wglCreateContext failed with error: {0}", Marshal.GetLastWin32Error());
success = Wgl.MakeCurrent(window.DeviceContext, Context.Handle);
if (!success)
{
Debug.Print("wglMakeCurrent failed with error: {0}. Retrying", Marshal.GetLastWin32Error());
System.Threading.Thread.Sleep(10);
}
}
}
else
{
Debug.Print("[WGL] CreateContext failed with error: {0}", Marshal.GetLastWin32Error());
}

if (!success)
{
Debug.WriteLine("[WGL] Failed to create temporary context");
}
}

public void Dispose()
{
if (Context != ContextHandle.Zero)
{
Wgl.MakeCurrent(IntPtr.Zero, IntPtr.Zero);
Wgl.DeleteContext(Context.Handle);
}
}
}

#region --- Contructors ---

public WinGLContext(GraphicsMode format, WinWindowInfo window, IGraphicsContext sharedContext,
int major, int minor, GraphicsContextFlags flags)
{
// There are many ways this code can break when accessed by multiple threads. The biggest offender is
// the sharedContext stuff, which will only become valid *after* this constructor returns.
// The easiest solution is to serialize all context construction - hence the big lock, below.
lock (SyncRoot)
lock (LoadLock)
{
if (window == null)
throw new ArgumentNullException("window", "Must point to a valid window.");
if (window.Handle == IntPtr.Zero)
throw new ArgumentException("window", "Must be a valid window.");

Debug.Print("OpenGL will be bound to window:{0} on thread:{1}", window.Handle,
System.Threading.Thread.CurrentThread.ManagedThreadId);

lock (LoadLock)
IntPtr current_context = Wgl.GetCurrentContext();
INativeWindow temp_window = null;
TemporaryContext temp_context = null;
try
{
if (current_context == IntPtr.Zero)
{
// Create temporary context to load WGL extensions
temp_window = new NativeWindow();
temp_context = new TemporaryContext(temp_window);
current_context = Wgl.GetCurrentContext();
if (current_context != IntPtr.Zero && current_context == temp_context.Context.Handle)
{
Wgl.LoadAll();
}
}

Debug.Print("OpenGL will be bound to window:{0} on thread:{1}", window.Handle,
System.Threading.Thread.CurrentThread.ManagedThreadId);

ModeSelector = new WinGraphicsMode(window.DeviceContext);
Mode = SetGraphicsModePFD(ModeSelector, format, (WinWindowInfo)window);

Expand Down Expand Up @@ -146,44 +169,57 @@ public WinGLContext(GraphicsMode format, WinWindowInfo window, IGraphicsContext
if (Handle == ContextHandle.Zero)
Debug.Print("failed. (Error: {0})", Marshal.GetLastWin32Error());
}
catch (EntryPointNotFoundException e) { Debug.Print(e.ToString()); }
catch (NullReferenceException e) { Debug.Print(e.ToString()); }
catch (Exception e) { Debug.Print(e.ToString()); }
}
}

if (Handle == ContextHandle.Zero)
{
// Failed to create GL3-level context, fall back to GL2.
Debug.Write("Falling back to GL2... ");
Handle = new ContextHandle(Wgl.CreateContext(window.DeviceContext));
if (Handle == ContextHandle.Zero)
{
// Failed to create GL3-level context, fall back to GL2.
Debug.Write("Falling back to GL2... ");
Handle = new ContextHandle(Wgl.CreateContext(window.DeviceContext));
if (Handle == ContextHandle.Zero)
throw new GraphicsContextException(
String.Format("Context creation failed. Wgl.CreateContext() error: {0}.",
Marshal.GetLastWin32Error()));
}

Debug.WriteLine(String.Format("success! (id: {0})", Handle));

// Todo: is this comment still true?
// On intel drivers, wgl entry points appear to change
// when creating multiple contexts. As a workaround,
// we reload Wgl entry points every time we create a
// new context - this solves the issue without any apparent
// side-effects (i.e. the old contexts can still be handled
// using the new entry points.)
// Sigh...
Wgl.LoadAll();
if (Handle == ContextHandle.Zero)
Handle = new ContextHandle(Wgl.CreateContext(window.DeviceContext));
if (Handle == ContextHandle.Zero)
throw new GraphicsContextException(
String.Format("Context creation failed. Wgl.CreateContext() error: {0}.",
Marshal.GetLastWin32Error()));
}

if (sharedContext != null)
Debug.WriteLine(String.Format("success! (id: {0})", Handle));
}
finally
{
Marshal.GetLastWin32Error();
Debug.Write(String.Format("Sharing state with context {0}: ", sharedContext));
bool result = Wgl.ShareLists((sharedContext as IGraphicsContextInternal).Context.Handle, Handle.Handle);
Debug.WriteLine(result ? "success!" : "failed with win32 error " + Marshal.GetLastWin32Error());
if (temp_context != null)
{
temp_context.Dispose();
temp_context = null;
}
if (temp_window != null)
{
temp_window.Dispose();
temp_window = null;
}
}
}

// Todo: is this comment still true?
// On intel drivers, wgl entry points appear to change
// when creating multiple contexts. As a workaround,
// we reload Wgl entry points every time we create a
// new context - this solves the issue without any apparent
// side-effects (i.e. the old contexts can still be handled
// using the new entry points.)
// Sigh...
Wgl.MakeCurrent(window.DeviceContext, Handle.Handle);
Wgl.LoadAll();

if (sharedContext != null)
{
Marshal.GetLastWin32Error();
Debug.Write(String.Format("Sharing state with context {0}: ", sharedContext));
bool result = Wgl.ShareLists((sharedContext as IGraphicsContextInternal).Context.Handle, Handle.Handle);
Debug.WriteLine(result ? "success!" : "failed with win32 error " + Marshal.GetLastWin32Error());
}
}

static ArbCreateContext GetARBContextFlags(GraphicsContextFlags flags)
Expand Down Expand Up @@ -231,7 +267,6 @@ public override void SwapBuffers()

public override void MakeCurrent(IWindowInfo window)
{
lock (SyncRoot)
lock (LoadLock)
{
bool success;
Expand Down Expand Up @@ -347,7 +382,7 @@ static bool IsValid(IntPtr address)
{
// See https://www.opengl.org/wiki/Load_OpenGL_Functions
long a = address.ToInt64();
bool is_valid = (a < -1 )|| (a > 3);
bool is_valid = (a < -1) || (a > 3);
return is_valid;
}

Expand Down Expand Up @@ -379,7 +414,7 @@ internal static GraphicsMode SetGraphicsModePFD(WinGraphicsMode mode_selector,
Functions.DescribePixelFormat(
window.DeviceContext, (int)mode.Index.Value,
API.PixelFormatDescriptorSize, ref pfd);

Debug.WriteLine(mode.Index.ToString());

if (!Functions.SetPixelFormat(window.DeviceContext, (int)mode.Index.Value, ref pfd))
Expand Down

0 comments on commit d8a4ca1

Please sign in to comment.