Jamieson's picture

V Sync to which monitor?

Hi,

I installed OpenTX yesterday and started programming OpenGL for the first time. My software uses two Winforms windows, one control form and a display window (Which contains the GLControl).

The program is for simple 2D animation where i have rendered a quad and i'm swapping textures to get the effect im looking for. The good news it works.
Both Winforms (control and display) are created on Screen 1, i then move the display window on to Screen 2 and maximise.

My question is, given that my monitors have different refresh rates, which vsync is being used to frame limit my displaywindow ? and do i need to select the screen in some way ?

Thanks Jamie


Comments

Comment viewing options

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

Windows and Linux vsync to the primary monitor. The primary monitor is the one that is identified as '1' in your monitor configuration.

Jamieson's picture

Thanks for the quick response, is there a way to make the GLControl vsync to the second monitor ?

Thanks again Jamie

the Fiddler's picture

As far as I know, the only way is to change the primary monitor. I've never seen any program vsyncing to the secondary monitor (including Windows Media Player and VLC), which leads me to believe that this is an OS limitation.

Alternatively, try setting both monitors to the same refresh rate.

Jamieson's picture

Thanks, i tried setting my screen to the primary but it doesnt solve the problem that im seeing.
The software i've written (below) shows that im achieving 60fps (with V-Sync) and more than 250fps with out V-Sync. But the actual visible on the screen does not always reflect this, some times a static image is displayed and some times the change in frames is jerky.

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Drawing.Imaging;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Diagnostics;
using OpenTK.Graphics.OpenGL;
using System.Threading;
using OpenTK;
 
 
namespace Display_Software
{
 
    public partial class Form2 : Form
    {
        private bool loaded;
        private double accumulator = 0;
 
        private int frameCounter = 0;
        private Stopwatch sw = new Stopwatch(); // available to all event handlers
        private int [] textures;
        private int current_frame=0;
        private int number_of_frames=0;
        public int fps;
        public bool running = false;
        //==============================================================================================================
        public Form2()
        {
            InitializeComponent();
            textures = new int[100];      
        }
        //==============================================================================================================
        private void glControl1_Load(object sender, EventArgs e)
        {
            loaded = true;
            SetupViewport();
            GL.ClearColor(Color.Black); 
 
            Application.Idle += Application_Idle;
            sw.Start();
 
            glControl1.Invalidate();
 
        }
        //==============================================================================================================
        private void SetupViewport()
        {
            int w = glControl1.Width;
            int h = glControl1.Height;
            GL.MatrixMode(MatrixMode.Projection);
            GL.LoadIdentity();
            GL.Ortho(0, w, 0, h, -1, 1); // Bottom-left corner pixel has coordinate (0, 0)
            GL.Viewport(0, 0, w, h); // Use all of the glControl painting area
            GL.Enable(EnableCap.Texture2D);
        }
        //==============================================================================================================
        private void Application_Idle(object sender, EventArgs e)
        {
            sw.Stop();
            double milliseconds = sw.Elapsed.TotalMilliseconds;
            sw.Reset();
            sw.Start();
 
            accumulator += milliseconds;
 
            if (accumulator > 1000)
            {
                fps = frameCounter;
                accumulator -= 1000;
                frameCounter = 0; 
            }
 
            if(glControl1.IsIdle)
            {
                glControl1.Invalidate();
            }
 
        }
        //==============================================================================================================
        private void glControl1_Resize(object sender, EventArgs e)
        {
            if (!loaded)return;
            SetupViewport();
            glControl1.Invalidate();
        }
        //==============================================================================================================
        private void glControl1_Paint(object sender, PaintEventArgs e)
        {
            if (!loaded)return;
            GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);
 
            GL.MatrixMode(MatrixMode.Modelview);          
 
            Blit(0, 0, glControl1.Width, glControl1.Height);
 
            glControl1.SwapBuffers();
 
            frameCounter++;
 
            Thread.Sleep(10);                     
 
        }
        //==============================================================================================================
        private void Blit(int x, int y, int width, int height)
        {
 
            GL.BindTexture(TextureTarget.Texture2D, textures[current_frame]);
            GL.Begin(BeginMode.Quads);
            GL.TexCoord2(0, 0); GL.Vertex2(x, y);
            GL.TexCoord2(1, 0); GL.Vertex2(x + width, y);
            GL.TexCoord2(1, 1); GL.Vertex2(x + width, y + height);
            GL.TexCoord2(0, 1); GL.Vertex2(x, y + height);
            GL.End();
 
            current_frame++;
            if (current_frame >= number_of_frames) current_frame = 0;
 
        }
        //==============================================================================================================
        public void UpdateImages(int count, Bitmap []bitmaps)
        {
            running = false;
            //erase the current textures
            if(count>=1)
                {
                for (int i = 0; i < number_of_frames; i++) GL.DeleteTexture(textures[i]);
 
                number_of_frames = count;
                for (int i = 0; i < count; i++)
                {
                    bitmaps[i].RotateFlip(RotateFlipType.RotateNoneFlipY);
                    textures[i] = GL.GenTexture();
                    GL.BindTexture(TextureTarget.Texture2D, textures[i]);
                    BitmapData data = bitmaps[i].LockBits(new System.Drawing.Rectangle(0, 0, bitmaps[i].Width, bitmaps[i].Height),
                    ImageLockMode.ReadOnly, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
                    GL.TexImage2D(TextureTarget.Texture2D, 0, PixelInternalFormat.Rgba, data.Width, data.Height, 0, OpenTK.Graphics.OpenGL.PixelFormat.Bgra, PixelType.UnsignedByte, data.Scan0);
                    bitmaps[i].UnlockBits(data);
 
                    GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (int)TextureMinFilter.Linear);
                    GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, (int)TextureMagFilter.Linear);
                }
                running = true;
 
            }
            current_frame = 0;
        }
        //=============================================================================================================
    }
}

Have i missed anything obvious ?

Thanks Jamie

the Fiddler's picture

Check GC.CollectionCount(0). Is this number correlated with the jerkiness?

If so, then you are seeing the effects of frequent garbage collections. Since you are calling Thread.Sleep(10), you are leaving 6.6ms for rendering under ideal conditions, making your application more sensitive to GC issues.

If you can determine that the GC is indeed the cause, you can avoid a significant amount of allocations by calling:

                glControl1_Paint(this, null);

directly, instead of:

                glControl1.Invalidate();

as the latter allocates memory.

Unfortunately, WinForms will always allocate memory for events (mouse movements, keyboard, paint events), so this issue is unavoidable to some extent (this is why GameWindow exists).

Jamieson's picture

Thanks , i'll give it a try.... and report back

Jamieson's picture

I tried swapping glControl.Invalidate() with glControl_Paint(null, null) but for some reason it massively reduces the frame rate.
Any other ideas ?

Thanks

Jamie

the Fiddler's picture

This doesn't really make any sense, since glControl.Invalidate() does some processing and then raises the Paint (the overhead comes from creating a new PaintEventArgs instance every time). Calling the Paint handler directly should increase frame rate, if anything. The standard game loop for WinForms looks like this:

void Application_Idle(object sender, EventArgs e)
{
    while (glControl1.IsIdle)
    {
        RenderFrame();
    }
}
 
void RenderFrame()
{
    GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);
    GL.MatrixMode(MatrixMode.Modelview);          
    Blit(0, 0, glControl1.Width, glControl1.Height);
 
    glControl1.SwapBuffers();
}
 
// This is not actually necessary when painting continuously in Application.Idle
void glControl1_Paint(object sender, PaintEventArgs e)
{
    RenderFrame();
}

This has been shown to be the most efficient game loop possible under WinForms.

Edit: the issue is probably that you are using if (glControl1.Idle) rather than while (glControl1.Idle). The Application.Idle event is raised only once whenever the internal event loop becomes empty. Your Invalidate() call would add a new event every time: Idle -> Paint -> Idle -> Paint. By removing that call, you go to: Idle -> Idle -> Idle, so you need to paint in a loop.

(Try adding a Console.WriteLine("Idle") message at the top of your Idle handler and see what happens when using Invalidate and when not).

Jamieson's picture

Excellent that fixed it, i changed from if (glControl1.Idle) to while (glControl1.Idle) and it is much more reliable.

Thanks for all your help.

Jamie

virabhadra's picture

Hello together,

I'm working on a screen saver with a rotating logo and the CubeMap effect.

I tried this code above for the loop: while(glControl1.Idle)
The program uses the maximum load of the system even for a simple model (some thousands of polygons).

Before I used the System.Windows.Forms.Timer class to refresh the scene.
Even when the refresh interval is defined very small (f.e. 10 ms) the window doesn't refresh faster than the monitor frequency, so it's good for performance, but movements are not smooth and it looks like without vsync.

Would you like, please, to give me some tips how to do it more realistic without system overloading?

Thnx

Dennis