dhakim's picture

Single Context Multiple System.Windows.Forms Controls

Hey all, I've been running down the insanity inducing road of one GraphicsContext used for multiple controls in an attempt to avoid problems with sharing textures between contexts when running on Intel cards. (There is a part of the code that needs to swap two views, this can be done by swapping the GLControls I have, but seems to flicker white when GLControls are removed from their container on Windows 7, it can also be done by swapping the GLControl references I draw to, but this breaks all of my texture indices on Intel cards which do not share texture memory)

I'm now at the point where I've managed to create GraphicsContext's for Forms and Panels that I can use in the paint methods of those forms and panels, but if I try to use a context I created for one panel in a second panel's paint (calling make current on the other panel's WindowInfo), I fail out with a GraphicsContextException and an Error 2000.

(This error is ERROR_INVALID_PIXEL_FORMAT according to http://msdn.microsoft.com/en-us/library/ms681386%28v=VS.85%29.aspx)

I'm running on a brand new Windows 7 machine with a brand new EVGA NVidia GeForce GTX 460, and while I haven't tried this on the intel laptops I'm developing for, if it doesn't work on my machine, I'm guessing its not going to work on them either :(

Here's the smallest code sample I've managed to get the issue with, hopefully I'm just missing a call you all can spot in seconds:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using OpenTK.Platform;
using OpenTK.Graphics;
using OpenTK.Graphics.OpenGL;
using System.Threading;
 
namespace SingleContextMultipleWindow
{
    public partial class Form1 : Form
    {
        IGraphicsContext context;
        IWindowInfo panelInfo;
        IWindowInfo panel2Info;
        public Form1()
        {
            InitializeComponent();
            Panel p = new Panel();
            p.Dock = DockStyle.Bottom;
            p.HandleCreated += new EventHandler(panel_HandleCreated);
            p.Paint += new PaintEventHandler(panel_Paint);
 
            Panel p2 = new Panel();
            p2.Dock = DockStyle.Right;
            p2.HandleCreated += new EventHandler(panel2_HandleCreated);
            p2.Paint +=new PaintEventHandler(panel2_Paint);
 
            this.Controls.Add(p);
            this.Controls.Add(p2);
        }
 
        void panel_HandleCreated(object sender, EventArgs e)
        {
            panelInfo = OpenTK.Platform.Utilities.CreateWindowsWindowInfo((sender as Control).Handle);
 
            context = new GraphicsContext(GraphicsMode.Default, panelInfo);
            context.MakeCurrent(panelInfo);
            (context as IGraphicsContextInternal).LoadAll();
            GraphicsMode panelGM = (context as IGraphicsContextInternal).Implementation.GraphicsMode;
            Console.WriteLine("Panel1Info: " + panelInfo);
            Console.WriteLine("Context Graphics Mode: " + panelGM);
        }
 
        void panel2_HandleCreated(object sender, EventArgs e)
        {
            panel2Info = OpenTK.Platform.Utilities.CreateWindowsWindowInfo((sender as Control).Handle);
            Console.WriteLine("Panel2Info: " + panel2Info);
        }
 
        void panel_Paint(object sender, PaintEventArgs e)
        {
            try
            {
                context.MakeCurrent(panelInfo); 
                GL.ClearColor(Color.Red);
                GL.Clear(ClearBufferMask.ColorBufferBit);
                GL.Flush();
                context.SwapBuffers();
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
                Console.WriteLine(ex.StackTrace);
            }
        }
 
        void panel2_Paint(object sender, PaintEventArgs e)
        {
            try
            {
                context.MakeCurrent(panel2Info); //GraphicsContextException Failed to make context current, error 2000
                GL.ClearColor(Color.Blue);
                GL.Clear(ClearBufferMask.ColorBufferBit);
                GL.Flush();
                context.SwapBuffers();
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
                Console.WriteLine(ex.StackTrace);
            }
        }
    }
}

From the output, you can see that both handle created events were fired, and that both WindowInfos seem to be properly constructed, (though I don't know if the parent (null) is a bad sign...), as does the context itself.
The code outputs

Panel1Info: Windows.WindowInfo: Handle 4785284, Parent (null)
Context Graphics Mode: Index: 8, Color: 32 (8888), Depth: 24, Stencil: 0, Samples: 0, Accum: 64 (16161616), Buffers: 2, Stereo: False
Panel2Info: Windows.WindowInfo: Handle 9307166, Parent (null)
Failed to make context 196608 current. Error: 2000
at OpenTK.Platform.Windows.WinGLContext.MakeCurrent(IWindowInfo window)
at OpenTK.Graphics.GraphicsContext.MakeCurrent(IWindowInfo window)
at SingleContextMultipleWindow.Form1.panel2_Paint(Object sender, PaintEventArgs e) in C:\TRXSVN\trunk\sentinel\proto\SingleContextMultipleWindow\Form1.cs:line 77

Looking at the form itself, you'll see red along the bottom, and nothing along the right side, meaning the panel1 paint worked properly, and the panel2_paint did nothing (except throw the exception above)

Am I not allowed to call MakeCurrent with a different WindowInfo (I'd swear I read that was how you moved contexts around)? Do I need to call any other functions? Any other ideas?


Comments

Comment viewing options

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

At first glance, this should work. Let me see if I can reproduce.

jdomnitz's picture

You need to call makeCurrent null on one window before calling makeCurrent with the context on another...I usually do it at the end of the paint routine.

dhakim's picture

Just tried, no luck :(

Calling context.MakeCurrent(null) immediately after both context.SwapBuffers() calls in the two paint methods does not appear to make a difference. I get identical output.

Also, I could be wrong, but I thought that MakeCurrent(null) was only for sharing a context between threads, which is not being done in this instance. Both panels should be being painted from the same thread.

the Fiddler's picture

Is panel_HandleCreated called more than once? This might lead to errors. Try adding HandleDestroyed event handlers and call Dispose() on the IWindowInfo and IGraphicsContext fields explicitly.

the Fiddler's picture

I can reproduce the issue on my system, investigating.

the Fiddler's picture

Apparently, you need to set the pixel format for every control explicitly. This entails an impossible amount of fragile code but fortunately the workaround is simple: create multiple GLControls with the same GraphicsMode and use one of their contexts to render all.

If you wish to render on a Panel, just dock a GLControl inside it with DockStyle.Fill.

dhakim's picture

Excellent, from my tests that seems to work great.

While I'm curious about why that works and Panels don't, its definitely easier to switch from multiple GLControls with their own contexts to using multiple GLControls with one context than to switch to multiple panels with one context anyway.

Thanks a lot.

(PS: Should I add inability to use panels/UserControls directly to the issue tracker?)

the Fiddler's picture

The underlying issue is that you need to set a pixel format before trying to make the OpenGL context current on a Control/UserControl. When you create a new GraphicsContext, you automatically set the pixel format for the underlying control (that's what the GraphicsMode parameter does). This doesn't happen when you switch the context to a different control, so you get an error. This explains why GLControl works: each GLControl is "primed" with a pixel format, so it's possible to juggle contexts between them.

In theory, it *might* be possible to fix this but, (a) it's a ton of work with uncertain outcome (is this supported on all operating systems?), (b) it might result in a speed penalty and (c) a trivial workaround exists (dock a GLControl and render into that). In fact, I've never seen an OpenGL toolkit that allows you to switch contexts like this - OpenTK is already ahead in flexibility in that regard!

c2woody's picture

Trying to play around with some code that got broken when moving rendering to completely separate threads, and the idea discussed here sounds interesting: multiple controls using one context. Would that work with separate threads as well, so each of the controls does a context.MakeCurrent(window-to-draw) in its own thread, or would that require MakeCurrent(null) calls on other threads? Or is this not possible at all and one rendering-thread has to be used that does the window switching (all for the same context)?

the Fiddler's picture

Ideally, try to keep a 1-1 relationship between contexts and threads. To make a single context current on a new thread you'll have to call MakeCurrent(null) on the previous, so that approach won't really work.