0

I'm working on an OSX app that is made of two NSOpengGLViews (leftGLView and rightGLView) placed side-by-side inside the NSWindow contentView. The two views don't share the NSOpenGLContext but are both double-buffered and sync the buffer swap with the monitor retrace.

A CVDisplayLink is setup and linked to the NSOpenGLContext and NSOpenGLPixelFormat of the first NSOpenGLView (say the left hand side one).

Inside the CVDisplayLink callback I draw to both views with the following code:

-(CVReturn)draw {

     NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];

     CGLLockContext([[leftGLView openGLContext] CGLContextObj]);
     [[leftGLView openGLContext] makeCurrentContext];
     ... draw code ...
     [[leftGLView openGLContext] flushBuffer];
     CGLUnlockContext([[leftGLView openGLContext] CGLContextObj]);

     CGLLockContext([[rightGLView openGLContext] CGLContextObj]);
     [[rightGLView openGLContext] makeCurrentContext];
     ... draw code ...
     [[rightGLView openGLContext] flushBuffer];
     CGLUnlockContext([[rightGLView openGLContext] CGLContextObj]);

     [pool release];
     return kCVReturnSuccess;
}

The issue with that approach is that the two flushBuffer are not always synchronized. The right hand side NSOpenGLView sometimes updates later than the left hand side (possibly during the next monitor refresh). This is not fully unexpected as the Apple documention about -flushBuffer states:

According to the swap interval context attribute (see NSOpenGLCPSwapInterval), the copy may take place during the vertical retrace of the monitor, rather than immediately after flushBuffer is called. An implicit glFlush is done by flushBuffer before it returns. For optimal performance, an application should not call glFlush immediately before calling flushBuffer. Subsequent OpenGL commands can be issued immediately after calling flushBuffer, but are not executed until the buffer copy is completed.

However, in this case I have two different NSOpenGLContext therefore flushing two buffers together should be possible.

To further investigate this, I assumed that -flushBuffer returns only after having swapped (or copied) the buffer and since this only happens in sync with the monitor retrace, the second call to flushBuffer would be too late to happen on that retrace. This could explain the behavior I'm observing. Therefore I tried to detach an auxiliary thread to perform the flushBuffer calls but it made no difference.

I have finally tried to use two different CVDisplayLinks, one for the left hand side GLView and the other for the right hand side one. However also this technique doesn't seem to solve the problem and adds the additional complication of keeping the two CVDisplayLink threads in sync.

Is there a way to have the two flushBuffer happening in sync between the two NSOpenGLViews?

Andrea3000
  • 1,018
  • 1
  • 11
  • 26
  • Make each context current to a different thread prior to swapping. Otherwise, with VSYNC enabled your framerate will be unnecessarily cut in half. – Andon M. Coleman Jun 01 '14 at 21:15
  • @AndonM.Coleman: Thank you for the reply. When I tried with two threads (one for each view) I did just that and it improved the situation but it didn't completely fixed the problem. In the example I posted I'm using just one thread but the framerate is 60fps as expected anyway. I don't understand way the framerate should be cut in half. – Andrea3000 Jun 01 '14 at 21:29
  • If you use VSYNC, then each swap will finish after VBLANK. The most naive implementation of buffer swapping with VSYNC is one where the swap operation itself blocks until VBLANK (your monitor's vertical retrace interval) rolls around. So in the worst case (assuming a 60 Hz refresh rate), two back-to-back swaps will take 33.33334 ms. In the best case, they will take a little over 16.66667. No matter what the case, one of your windows will always be 1 frame behind the other unless you drive the swap-chain from two separate threads. – Andon M. Coleman Jun 01 '14 at 21:34
  • @AndonM.Coleman: Thank you, I understand now. Do you know why sometimes one view is still one frame behind even when I drive the swap from two separate threads? Is much better than with a single thread but it's still not perfect. – Andrea3000 Jun 01 '14 at 21:53
  • There still is no guarantee that the two threads will swap buffers far enough in advance to complete before VBLANK. You can see this even in single-window applications, if they swap buffers about 1ms before VBLANK then the frame may not complete in time, and the end result is an old frame displayed for 2 frames and the new frame slightly late. One thing that you could consider is actually doing this all in a single thread, but only using VSYNC on one of the windows. Assuming the buffer swap for the 1st blocks until VBLANK and you can finish the frame for the 2nd window in time, this might work – Andon M. Coleman Jun 01 '14 at 22:17
  • @AndonM.Coleman: Let's consider that I enable VSYNC only for the first OpenGL context and that I perform all the drawing and swapping from a single thread. The order of operation would be: draw first window, swap first window, draw second window, swap second window. When I call -flushBuffer on the first window, glFlush is implicitly called when the function returns and the drawing of the first window actually takes place. Then the buffer swap of the first window should occur but this will be executed only during monitor retrace and the thread will be blocked until VBLANK (as you pointed out) – Andrea3000 Jun 02 '14 at 12:36
  • In the meantime, on the CPU side, the -flushBuffer call on the second window has triggered glFlush and therefore the second window draws itself to the back buffer. At this point it waits for the monitor retrace to swap the buffer but this can't take place becuase the buffer swap of the first window will block the thread until after VBLANK. Therefore, how can the second window be updated in time? Even if the buffer swap of the second window takes a svery small amount of time, it will start after VBLANK anyway, isn't it? – Andrea3000 Jun 02 '14 at 12:43
  • No, that is not quite how VSYNC works. VSYNC will not swap buffers until the start of a new vertical retrace. It does not take a full 16 ms to swap those buffers, so it returns pretty quickly after VBLANK, probably 2-3 ms at the absolute most assuming it has to ***draw*** the backbuffer in addition to swapping it. That leaves you with another 13-14 ms (@60 Hz) to draw your second window before you have to worry about tearing; very few frames will take 14 ms (given the fact that you already draw at 120 FPS). If you have more questions, it might be better to move this into chat. – Andon M. Coleman Jun 02 '14 at 16:58
  • @AndonM.Coleman: Thank you for the explanation. I will do some tests during the weekend and I will get back to you. – Andrea3000 Jun 03 '14 at 21:24

0 Answers0