thegeneralsolution's picture

Trouble getting OpenTK to work on multiple monitors

I am writing a screensaver using C# and OpenTK.

I began with the example code available at the link below which is a completely basic screensaver project in Visual Studio.

http://www.harding.edu/fmccown/screensaver/screensaver.html

The screensaver is already setup to work with multiple monitors, and I am trying to get it to use OpenTK.

Here are 7 short steps you can use to reproduce my problem:

1. Open the ScreenSaver.sln solution
2. Double click 'ScreenSaverForm.cs' to bring up the form editor.
3. In the toolback, I drag a small OpenTK GlControl window onto the form.
4. right click 'ScreenSaverForm.cs' and click 'View Code'
5. Beneath all the existing 'using' statements I add 'using OpenTK.Graphics.OpenGL;' (I am using immediate mode)
6. modify the existing move_TimerTick() function to be the following:

  private void moveTimer_Tick(object sender, System.EventArgs e)
        {
            // Move text to new location
            textLabel.Left = rand.Next(Math.Max(1, Bounds.Width - textLabel.Width));
            textLabel.Top = rand.Next(Math.Max(1, Bounds.Height - textLabel.Height));
 
            GL.ClearColor(0.0f, 1.0f, 0.0f, 1.0f);
            GL.Clear(ClearBufferMask.ColorBufferBit);
 
            glControl1.SwapBuffers();
 
        }

That is, just clear the screen to green.

7. Run the application in debug mode. For me, if I run it with one monitor, the gl control is cleared to green (As soon as the timer ticks). But if I run it a system with 2 monitors, only one of the two turns green.

Any advice or insight on what's happening would be greatly appreciated.

Thanks!


Comments

Comment viewing options

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

OpenGL commands are directed to the OpenGL context that is "current" on the thread where the command is issued. If you have multiple contexts, as is the case here, you need to call GLControl.MakeCurrent() to change the current context. For instance:

        private void moveTimer_Tick(object sender, System.EventArgs e)
        {
            glControl1.MakeCurrent();
 
            // Move text to new location
            textLabel.Left = rand.Next(Math.Max(1, Bounds.Width - textLabel.Width));
            textLabel.Top = rand.Next(Math.Max(1, Bounds.Height - textLabel.Height));
 
            GL.ClearColor(0.0f, 1.0f, 0.0f, 1.0f);
            GL.Clear(ClearBufferMask.ColorBufferBit);
 
            glControl1.SwapBuffers();
        }

Be extra careful when combining events with OpenGL rendering. If you call glControl1.MakeCurrent in moveTimer_Tick, this does not mean that e.g. glControl1.Paint will still have a current context. I'd suggest moving all OpenGL commands into a separate class or function. Something like:

class GLRenderer
{
    public void Render(IGraphicsContext context, IWindowInfo window)
    {
        if (!context.IsCurrent)
        {
            context.MakeCurrent(window);
        }
 
        GL.ClearColor(0.0f, 1.0f, 0.0f, 1.0f);
        GL.Clear(ClearBufferMask.ColorBufferBit);
 
        context.SwapBuffers();
    }
}

and have your screensaver Form call into this as necessary:

class ScreenSaverForm : Form
{
    readonly GLRenderer Renderer = new GLRenderer();
 
    public ScreenSaverForm()
    {
        InitializeComponent();
 
        Paint += (sender, e) => Render();
        moveTimer_Tick += (sender, e) => Render();
    }
 
    void Render()
    {
        Renderer.Render(glControl1.Context, glControl1.WindowInfo);
    }
}