Kevitus's picture

Making multiple glControls show the exact same thing

Hey guys.
I'm currently developing a teleprompting software and I use OpenTK for smoot scrolling of pictures (That were copied from a richtextbox control).
Now this is working fine but now I need to have multiple (or at least two) glControls that are showing the same output. I've been doing this with gl_copypixels but this solution is not performant enough. Is there any way to make two glControls current at the same time or anything else that might make all this quite easier?

Thanks for your help!


Comments

Comment viewing options

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

What is your bottleneck?

  • Calling MakeCurrent()?
  • Calling GL.CopyPixels()?
  • Rendering the scene?

Depending on the bottleneck, different approaches may be applicable. For example, you can use framebuffer objects and avoid GL.CopyPixels(). This allows you to render the scene directly to a texture and have both GLControls bind this texture to a fullscreen quad. The downside is that FBOs are not as widely supported as CopyPixels (they are supported by all Ati and Nvidia cards but only a few recent Intel chips).

If MakeCurrent() is the bottleneck, you can make the GLControls current on two different threads and avoid all context switches. On the downside, this means that you won't be able to use any OpenGL commands in typical WinForms events (Paint, Resize, WinForms timers, etc) - this is not nearly as bad as it sounds, just have the new thread run a repaint loop continuously (WinForms events will still occur in the main thread, the second thread will only handle OpenGL-related tasks).

If the bottleneck is the scene itself, then you'll either have to make the scene simpler (reduce resolution maybe?) or get more powerful hardware...

Kevitus's picture

My current bottleneck is that I have two glControls, where one is the mainrenderer and the second is a mirror of the mainrenderer, that displays a fullscreen quad textured with the copypixel of the mainrenderer. Now that works ok with one exception. The mainrenderer triggers the invalidation of the mirror as well to make it synch. However there is sometimes one frame where GL loads two new textures. The mainrenderer is loading the new portion of the text (as an image) and the mirror is doing his copypixels. Now this frame is stuttering and prevents the whole thing from running smooth throughout.
I can't come up with a decent solution for this :(

Thanks for your help in advance!

jmanson's picture

This may be a stupid comment, but have you tried turning VSync off? I had stuttering issues with VSync before.

Kevitus's picture

Hi all!
Yes I've tried switching Vsync off and yeah this boosts up the framerate but since vsync is off the result is not satisfying.
Right now I've managed it so that I create a GameWindow with a size that covers both screens and shows 2x2 quads for each monitor. Now the problem is that the perfomance is still kind of varying for different computers. I can't believe that 4 quads is too much for a computer with a genuine graphic card.
Here is my onRenderFrame event if that helps, maybe I'm doing something wrong.

        protected override void  OnRenderFrame(FrameEventArgs e)
        {
            base.OnRenderFrame(e);
 
            if (scrollPos >= 1.0) // scroll down
            {
                if (pageUp < numberOfPages+2)
                {
                    scrollPos = (scrollPos - 1.0); // reset position
                    GL.BindTexture(TextureTarget.Texture2D, Textures[idx]);
                    UpdateTexture(pageUp);
                    pageUp++;
                    pageDn++;
                    idx = (idx + 1) % 2;
                }
            }
            else
                if (scrollPos < 0.0) //scroll up
                {
                    if (pageDn >= 0)
                    {
                        scrollPos = scrollPos + 1.0; //
                        GL.BindTexture(TextureTarget.Texture2D, Textures[(idx + 1) % 2]);
                        UpdateTexture(pageDn);
                        pageDn--;
                        pageUp--;
                        idx = (idx + 1) % 2;
                    }
                }
            scrollPos += (scrollSpeed * e.Time);
 
                GL.PushMatrix();
                GL.Translate(0f, scrollPos, 0f);
 
                GL.BindTexture(TextureTarget.Texture2D, Textures[idx]);
 
                //Screen 1
                GL.Begin(BeginMode.Quads);
                    GL.TexCoord2(0.0, 0.0);
                    GL.Vertex2(val3, 1.0);
                    GL.TexCoord2(0.0, 600.0 / 1024.0);
                    GL.Vertex2(val3, 0.0);
                    GL.TexCoord2(800.0 / 1024.0, 600.0 / 1024.0);
                    GL.Vertex2(val4, 0.0);
                    GL.TexCoord2(800.0 / 1024.0, 0.0);
                    GL.Vertex2(val4, 1.0);
                GL.End();
 
                //Screen 2
                GL.Begin(BeginMode.Quads);
                    GL.TexCoord2(0.0, 0.0);
                    GL.Vertex2(val1, 1.0);
                    GL.TexCoord2(0.0, 600.0 / 1024.0);
                    GL.Vertex2(val1, 0.0);
                    GL.TexCoord2(800.0 / 1024.0, 600.0 / 1024.0);
                    GL.Vertex2(val2, 0.0);
                    GL.TexCoord2(800.0 / 1024.0, 0.0);
                    GL.Vertex2(val2, 1.0);
                GL.End();
 
                GL.BindTexture(TextureTarget.Texture2D, Textures[(idx + 1) % 2]);
 
                //Screen 1
                GL.Begin(BeginMode.Quads);
                    GL.TexCoord2(0.0, 0.0);
                    GL.Vertex2(val3, -0.0);
                    GL.TexCoord2(0.0, 600.0 / 1024.0);
                    GL.Vertex2(val3, -1.0);
                    GL.TexCoord2(800.0 / 1024.0, 600.0 / 1024.0);
                    GL.Vertex2(val4, -1.0);
                    GL.TexCoord2(800.0 / 1024.0, 0.0);
                    GL.Vertex2(val4, 0.0);
                GL.End();
 
                //Screen 2
                GL.Begin(BeginMode.Quads);
                    GL.TexCoord2(0.0, 0.0);
                    GL.Vertex2(val1, -0.0);
                    GL.TexCoord2(0.0, 600.0 / 1024.0);
                    GL.Vertex2(val1, -1.0);
                    GL.TexCoord2(800.0 / 1024.0, 600.0 / 1024.0);
                    GL.Vertex2(val2, -1.0);
                    GL.TexCoord2(800.0 / 1024.0, 0.0);
                    GL.Vertex2(val2, 0.0);
                GL.End();
 
                GL.PopMatrix();
 
                this.SwapBuffers();
 
            }

idx swaps everytime a new texture is loaded, making it possible for me to need only two quads per monitor while scrolling down the text.

Any suggestions?

the Fiddler's picture

The quads are probably not the bottleneck. What resolutions are we talking about? How large are the textures? What graphics cards?

My suggestion is to use a profiler on your application and see which part is limiting performance (SlimTune is a free profiler that is looking good). There are ways to improve performance but we need to know the bottleneck first - my guess is that the issue lies either in UpdateTexture (GL.TexImage2D / GL.TexSubImage2D, to be exact) and/or monitor resolution.

Kevitus's picture

Ok, the resolution is 800x600,16bit@60 on both. The graphic card on the computer with issues is: Nvidia GeForce 2 MX/MX 400. I know it's not the best graphics card, but there are similar applications like mine that run smooth on this computer. The textures are 800x600, but are scaled to a power-of-two size to ensure better compatibility.

On the specified computer the text won't run smooth there are no large hickups but it's just not overall smooth. I've tried different framerates as well and it works best when I use a framerate like 30,60,120 (probably because of the 60Hz) but it's still not smooth.

I'm gonna download the profiler now and look what I can find.

Here's my UpdateTexture:

        private void UpdateTexture(int no)
        {
            if (no >= bmps.Length) return;
            bmp = bmps[no];
            bmpData = bmp.LockBits(rec, ImageLockMode.ReadOnly, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
 
            GL.TexSubImage2D(TextureTarget.Texture2D, 0, 0, 0, bmpData.Width, bmpData.Height,
            OpenTK.Graphics.OpenGL.PixelFormat.Bgra,
            PixelType.UnsignedByte, bmpData.Scan0);
            bmp.UnlockBits(bmpData);
        }
the Fiddler's picture

800x600 or even 1600x600 should be doable by a Geforce 2MX. As a test, try limiting the texture size in GL.TexSubImage2D to bmpData.[Width|Height] / 2 or even / 4 - this would help identify if there's a bottleneck at that part.

Moreover make sure to leave the framerate unlimited when calling GameWindow.Run(). I don't know if that's the case here, but using both vsync and the frame limiter can cause problems. In other words:

using (GameWindow gw = new GameWindow())
{
    gw.Run(30); // instead of gw.Run(30, 60);
}

Finally, try logging frame times in OnRenderFrame (Console.WriteLine(e.Time)). For smooth 60Hz, they should be consistenly below 16.67ms. (If not, it's best to go for smooth 30Hz - or 33.33ms - rather than 60Hz with occasional frame drops).

Kevitus's picture

I've run the profiler and this is my result. I don't know what this means at all:

I'm gonna try your suggestions now and look what happens.

AttachmentSize
profiler.PNG54.02 KB
Kevitus's picture

I've logged the framerate and its nowhere near 16,67ms.
I'm getting results in the fashion of:

0,0203017
0,07677
0,0313078
0,0310908
0,0468942
0,0469883
0,0310975
0,0156413
0,0468291
0,04694
0,0155908
0,0314118
0,0482455
0,0140165
0,0468428
0,0313098
0,0469104
0,0311785

Another hint might be that the lacking computer is a single core, the computer where I'm using csharp at is a dualcore and everything is running smooth there.

longjoel's picture

A couple of ideas you may find helpfull.

GL Copy Pixels is incredibly expensive. It may actually be cheaper to draw the entire scene over again in the 2nd window.

Render to a texture in first place, then just draw the texture twice using system calls instead of openGL. Not sure how this works with .NET and OpenTK, but in win32, it's pretty trivial. http://msdn.microsoft.com/en-us/library/ms970768.aspx

What platform are you using?