The GraphicsContext class

[Introduction]

The OpenTK.Graphics.GraphicsContext is a cross-platform wrapper around an OpenGL context. The context routes your OpenGL commands to the hardware driver for execution - which means you cannot use any OpenGL commands without a valid GraphicsContext.

[Constructors]

OpenTK creates a GraphicsContext automatically as part of the GameWindow and GLControl classes:

  • You can specify the desired GraphicsMode of the context using the mode parameter. Use GraphicsMode.Default to set a default, compatible mode or specify the color, depth, stencil and anti-aliasing level manually.
  • You can specify the OpenGL version you wish to use through the major and minor parameters. As per the OpenGL specs, the context will use the highest version that is compatible with the version you specified. The default values are 1 and 0 respectively, resulting in a 2.1 context.
  • You can request an embedded (ES) context by specifying GraphicsContextFlags.Embedded to the flags parameter. The default value will construct a desktop (regular OpenGL) context.
  • If you are creating the context manually, you must specify a valid IWindowInfo instance to the window parameter (see below). This is the default window the GraphicsContext will draw on and can be modified later using the MakeCurrent method.

Examples:

// Creates a 1.0-compatible GraphicsContext with GraphicsMode.Default
GameWindow window = new GameWindow();
 
// Creates a 3.0-compatible GraphicsContext with 32bpp color, 24bpp depth
// 8bpp stencil and 4x anti-aliasing.
GLControl control = new GLControl(new GraphicsMode(32, 24, 8, 4), 3, 0);

Sometimes, you might wish to create a second context for your application. A typical use case is for background loading of resources. This is very simple to achieve:

// The new context must be created on a new thread
// (see remarks section, below)
// We need to create a new window for the new context.
// Note 1: new windows remain invisible unless you call
//         INativeWindow.Visible = true or IGameWindow.Run()
// Note 2: invisible windows fail the pixel ownership test.
//         This means that the contents of the front buffer are undefined, i.e.
//         you cannot use an invisible window for offscreen rendering.
//         You will need to use a framebuffer object, instead.
// Note 3: context sharing will fail if the main context is in use.
// Note 4: this code can be used as-is in a GLControl or GameWindow.
 
EventWaitHandle context_ready = new EventWaitHandle(false, EventResetMode.AutoReset);
Context.MakeCurrent(null);
 
Thread thread = new Thread(() =>
{
    INativeWindow window = new NativeWindow();
    IGraphicsContext context = new GraphicsContext(GraphicsMode.Default, window.WindowInfo);
    context.MakeCurrent(window.WindowInfo);
 
    while (window.Exists)
    {
         window.ProcessEvents();
 
         // Perform your processing here
 
         Thread.Sleep(1); // Limit CPU usage, if necessary
    }
});
thread.IsBackground = true;
thread.Start();
 
context_ready.WaitOne();
MakeCurrent();

If necessary, you can also instantiate a GraphicsContext manually. For this, you will need to provide an amount of platform-specific information, as indicated below:

using OpenTK.Graphics;
using OpenTK.Platform;
using Config = OpenTK.Configuration;
using Utilities = OpenTK.Platform.Utilities;
 
// Create an IWindowInfo for the window we wish to render on.
// handle - a Win32, X11 or Carbon window handle
// display - the X11 display connection
// screen - the X11 screen id
// root - the X11 root window
// visual - the desired X11 visual for the window
IWindowInfo wi = null;
if (Config.RunningOnWindows)
    wi = Utilities.CreateWindowsWindowInfo(handle);
else if (Config.RunningOnX11)
    wi = Utilities.CreateX11WindowInfo(display, screen, handle, root, visual);
else if (Config.RunningOnMacOS)
    wi = Utilities.CreateMacOSCarbonWindowInfo(handle, false, false);
 
// Construct a new IGraphicsContext using the IWindowInfo from above.
IGraphicsContext context = new GraphicsContext(GraphicsMode.Default, wi);

Finally, it is possible to instantiate a 'dummy' GraphicsContext for any OpenGL context created outside of OpenTK. This allows you to use OpenTK.Graphics with windows created through SDL or other libraries:

IntPtr external_context = ...; // create or retrieve existing third-party context handle
GraphicsContext opentk_context = new GraphicsContext(
    new ContextHandle(external_context),
    null);

You can now use OpenTK GL functions normally.

If external_context is not a handle to a WGL, GLX or AGL/NSOpenGL/CGL context, you will have to specify a custom GetAddress and GetCurrentContext implementation in terms of the toolkit you are using. For instance, when using SDL2, the context handle returned by SDL_GL_CreateContext points to a SDL-specific structure. In this case:

IntPtr external_context = SDL_GL_CreateContext(...);
GraphicsContext opentk_context = new GraphicsContext(
    new ContextHandle(external_context),
    (name) => { return SDL_GL_GetProcAddress(name); }, // implement GetAddress via SDL
    () => { return SDL_GL_GetCurrentContext(); }); // implement GetCurrentContext via SDL

Note that GraphicsContext functions like context.SwapBuffers() will have no effect on external contexts. You *must* use the third-party toolkit to manage the external context.

A common use-case is integration of OpenGL 3.x through OpenTK.Graphics into an existing application.

[Remarks]

A single GraphicsContext may be current on a single thread at a time. All OpenGL commands are routed to the context which is current on the calling thread - issuing OpenGL commands from a thread without a current context may result in a GraphicsContextMissingException. This is a safeguard placed by OpenTK: under normal circumstances, you'd get an abrupt and unexplained crash.

[Methods]

  • MakeCurrent

    You can use the MakeCurrent() instance method to make a context current on the calling thread. If a context is already current on this thread, it will be replaced and made non-current. A single context may be current on a single thread at any moment - trying to make it current on two or more threads will result in an error. You can make a context non-current by calling MakeCurrent(null) on the correct thread.

    To retrieve the current context use the GraphicsContext.CurrentContext static property.

    If you wish to use OpenGL on multiple threads, you can either:

    • create one OpenGL context for each thread, or
    • use MakeCurrent() to make move a single context between threads.

    Both alternatives can be quite complicated to implement correctly. For this reason, it is usually advisable to restrict all OpenGL commands to a single thread, typically your main application thread, and use asynchronous method calls to invoke OpenGL from different threads. The GLControl provides the GLControl.BeginInvoke() method to simplify asynchronous method calls from secondary threads to the main System.Windows.Forms.Application thread. The GameWindow does not provide a similar API.

    To use multiple contexts on a single thread, call MakeCurrent to select the correct context prior to any OpenGL commands. For example, if you have two GLControls on a single form, you must call MakeCurrent() on the correct GLControl for each Load, Resize or Paint event.

    GLControl.MakeCurrent() and GameWindow.MakeCurrent() are instance methods that simplify the handling of contexts.

  • SwapBuffers

    OpenTK creates double-buffered contexts by default. Single-buffered contexts are considered deprecated, since they do not work correctly with compositors found on modern operating systems.

    A double-buffered context offers two color buffers: a "back" buffer, where all rendering takes place, and a "front" buffer which is displayed to the user. The SwapBuffers() method "swaps" the front and back buffers and displays the result of the rendering commands to the user. The contents of the new back buffer are undefined after the call to SwapBuffers().

    The typical rendering process looks similar to this:

    // Clear the back buffer.
    GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);
     
    // Issue rendering commands, like GL.DrawElements
     
    // Display the final rendering to the user
    GraphicsContext.CurrentContext.SwapBuffers();

    Note that caching the current context will be more efficient than retrieving it through GraphicsContext.CurrentContext. For this reason, both GameWindow and GLControl use a cached GraphicsContext for efficiency.

[Stereoscopic rendering]

You can create a GraphicsContext that supports stereoscopic rendering (also known as "quad-buffer stereo"), by setting the stereo parameter to true in the context constructor. GameWindow and GLControl also offer this parameter in their constructors.

Contexts that support quad-buffer stereo distinguish the back and front buffers between "left" and "right" buffer. In other words, the context contains four distinct color buffers (hence the name): back-left, back-right, front-left and front-right. Check out the stereoscopic rendering page for more information ([Todo: add article and link]).

Please note that quad-buffer stereo is typically not supported on consumer video cards. You will need a workstation-class video card, like Ati's FireGL/FirePro or Nvidia's Quadro series, to enable stereo rendering. Trying to enable stereo rendering on consumer video cards will typically result in a context without stereo capabilities.

[Accessing internal information]

GraphicsContext hides an amount of low-level, implementation-specific information behind the IGraphicsContextInternal interface. This information includes the raw context handle, the platform-specific IGraphicsContext implementation and methods to initialize OpenGL entry points (GetAddress() and LoadAll()).

To access this information, cast your GraphicsContext to IGraphicsContextInternal:

IntPtr raw_context_handle = (my_context as IGraphicsContextInternal).Context.Handle;
IntPtr function_address = (my_context as IGraphicsContextInternal).GetAddress("glGenFramebufferEXT");

Using an external OpenGL context with OpenTK

Starting with version 0.9.1, OpenTK requires the existence of an OpenGL context prior to the initialization of the OpenGL subsystem. In other words, you cannot use any OpenGL methods prior to the creation of a GraphicsContext.

If you create the OpenGL context through an external library (for example SDL or GTK#), you will need to inform OpenTK of the context's existence using the GraphicsContext.CreateDummyContext() static method. This method will return a new GraphicsContext instance for the context that is current on the calling thread. Optionally, you can pass the handle (IntPtr) of a specific external context to CreateDummyContext; in this case, the external context need not be current on the calling thread.

You will typically call this method as soon as the external context is created. For example, using Tao.Glfw:

Tao.Glfw.glfwOpenWindow(640, 480, 8, 8, 8, 8, 16, 0, Tao.Glfw.GLFW_WINDOW);
OpenTK.Graphics.GraphicsContext.CreateDummyContext();
 
// You may now use OpenTK.Graphics methods normally.

Please note that it is an error to call CreateDummyContext() multiple times for the same external context.