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");

Comments

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.
jakesee's picture

How do I create and attach a print device context for OpenGL to draw onto?

thorrablot's picture

But, both stormdrums and my code call the OpenTK GraphicsContext ctors, but still required the call to LoadAll() - here's my code from a sample Winform app:

        private void panel1_Layout(object sender, LayoutEventArgs e)
        {
            // Attempt to create our device for a 30-bit RGBA
            ColorFormat cf = new ColorFormat(10, 10, 10, 2);
            ColorFormat accCf = new ColorFormat(0, 0, 0, 0);
            int depthBufferBits = 0;
            int stencilBufferBits = 0;
            int fsaaSamples = 0;
            int buffers = 2;
            bool isStereo = false;
            Console.WriteLine("Requesting GraphicsMode.ColorFormat: [{0}]", cf.ToString());
            GraphicsMode gm = new GraphicsMode(cf, depthBufferBits, stencilBufferBits, fsaaSamples, accCf, buffers, isStereo);
            _iWin1 = Utilities.CreateWindowsWindowInfo(e.AffectedControl.Handle);
            _gc1 = new GraphicsContext(gm, _iWin1 );
            // Inspect acquired context to see what actual GraphicsMode is
            Console.WriteLine("Actual GraphicsMode.ColorFormat: [{0}]", _gc1.GraphicsMode.ToString());
 
            // Make this our current
            _gc1.MakeCurrent(_iWin1);
 
            // Mystery code from forum
            //(_gc1 as IGraphicsContextInternal).LoadAll();  // <-- must uncomment to work
 
            loaded1 = true;
            Console.WriteLine("panel1: GraphicsMode: {0}", _gc1.GraphicsMode.ToString());
            GL.ClearColor(Color.PaleGoldenrod);
            SetupViewport(panel1.Width, panel1.Height);
        }
 
 
        private GraphicsContext _gc1 = null;
        private GraphicsContext _gc2 = null;
        private IWindowInfo _iWin1 = null;
        private IWindowInfo _iWin2 = null;
 
        private void panel1_Paint(object sender, PaintEventArgs e)
        {
            if (!loaded1) // Play nice
                return;
 
            _gc1.MakeCurrent(_iWin1);
            GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);
 
            GL.MatrixMode(MatrixMode.Modelview);
            GL.LoadIdentity();
            Panel p = sender as Panel;
            PaintGradient(p.Width, p.Height, 8);
            _gc1.SwapBuffers();
        }
the Fiddler's picture

LoadAll() is called automatically when you create a GameWindow/GLControl/GraphicsContext. You only ever need to call this when using an 'external' context, i.e. when you call wglCreateContext et al yourself or through a 3rd-party library.

All OpenGL calls are routed through delegates (even GL1.1 stuff such as GL.Clear()) because each operating system behaves differently in this regard. Treating everything as a (potential) extension is the only way to achieve consistent behavior in the library.

thorrablot's picture

I'm glad you posted this, as I encountered the same problem. I didn't see any reference to the "LoadAll()" being required anywhere in the documentation - but that certainly made the difference between OpenTK throwing an exception on the first GL.ClearColor command and it succeeding. I'm at a loss to understand why this was necessary (extensions are required for ancient fixed-pipeline operations like GLClear?)

For a C#/Windows/WinForm user attempting to adopt OpenTK, the manual seems to be sparse on examples. I also have been wondering about the state of the sample browser (which appears mostly broken), and various other stale links on this site to partial documentation. Is there a good source to get material that's up-to-date? Is there any material that is specifically for the WinForms user?

That said - when things work, they do work well, and I've been impressed with what I've browsed of the source so far.

Thanks!

stormdrums's picture

Problem solved!
i missed the command before the line where the exception happens.

(m_context as IGraphicsContextInternal).LoadAll();

stormdrums's picture

I tried to use this code to add OpenGL to a current application. I have a GLControl Version which works well, but i can't get this to work. i'm using windows.
this is just 'testcode' to get it running, it doesn't make too much sense.
Mabye anyone has a small working example of creating the graphicscontext manually instead of using GLControl in a WinForms C# Project.

            IWindowInfo m_wi;
            IGraphicsContext m_context;
            m_wi = Utilities.CreateWindowsWindowInfo(pictureBox1.Handle);
            // Construct a new IGraphicsContext using the IWindowInfo from above.
            m_context = new GraphicsContext(GraphicsMode.Default, m_wi);
            m_context.MakeCurrent(m_wi);
            GL.Clear(ClearBufferMask.ColorBufferBit); //"NullReferenceException was unhandled" is thrown here
            GL.MatrixMode(MatrixMode.Projection);
            GL.LoadIdentity();
            GL.Ortho(0, pictureBox1.Width, pictureBox1.Height, 0, -1, 1);
            TexUtil.InitTexturing();
            m_textureID = TexUtil.CreateTextureFromFile("Untitled.png");
            GL.BindTexture(TextureTarget.Texture2D, m_textureID);
            GL.Begin(BeginMode.Quads);
            GL.TexCoord2(0, 0); GL.Vertex2(0, 0);
            GL.TexCoord2(1, 0); GL.Vertex2(pictureBox1.Width, 0);
            GL.TexCoord2(1, 1); GL.Vertex2(pictureBox1.Width, pictureBox1.Height);
            GL.TexCoord2(0, 1); GL.Vertex2(0, pictureBox1.Height);
            GL.End();
            m_context.SwapBuffers();

Thanks in advance!