
Single Context Multiple System.Windows.Forms Controls
Posted Thursday, 7 October, 2010 - 00:19 by dhakim inHey 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
Re: Single Context Multiple System.Windows.Forms Controls
At first glance, this should work. Let me see if I can reproduce.
Re: Single Context Multiple System.Windows.Forms Controls
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.
Re: Single Context Multiple System.Windows.Forms Controls
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.
Re: Single Context Multiple System.Windows.Forms Controls
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.
Re: Single Context Multiple System.Windows.Forms Controls
I can reproduce the issue on my system, investigating.
Re: Single Context Multiple System.Windows.Forms Controls
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.
Re: Single Context Multiple System.Windows.Forms Controls
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?)
Re: Single Context Multiple System.Windows.Forms Controls
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!
Re: Single Context Multiple System.Windows.Forms Controls
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)?
Re: Single Context Multiple System.Windows.Forms Controls
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.