diff options
author | Sven Gothel <[email protected]> | 2012-04-22 05:12:16 +0200 |
---|---|---|
committer | Sven Gothel <[email protected]> | 2012-04-22 05:12:16 +0200 |
commit | 9d522e77a9ac1f85c57236f00d5432e671f9169c (patch) | |
tree | 79c86efc114560393c6e870f55365c2726307130 /src | |
parent | 218d67fc0222d7709b21c45792d44501351939c4 (diff) |
Completing swap-interval implementation for OSX's CALayer usage. Closing Bug 555
- Based on Andres Colubri's initiative and commit 218d67fc0222d7709b21c45792d44501351939c4.
- Reading real screen refresh rate ('stolen' from NEWT)
- Properly handling swap-interval and vsync-to in native code
- Increasing accuracy vsync-to to microseconds
Tested manually w/ TestGearsES2AWT.
Diffstat (limited to 'src')
7 files changed, 150 insertions, 59 deletions
diff --git a/src/jogl/classes/jogamp/opengl/macosx/cgl/MacOSXCGLContext.java b/src/jogl/classes/jogamp/opengl/macosx/cgl/MacOSXCGLContext.java index 7d7dae950..3cd4b340c 100644 --- a/src/jogl/classes/jogamp/opengl/macosx/cgl/MacOSXCGLContext.java +++ b/src/jogl/classes/jogamp/opengl/macosx/cgl/MacOSXCGLContext.java @@ -396,11 +396,12 @@ public abstract class MacOSXCGLContext extends GLContextImpl class NSOpenGLImpl implements GLBackendImpl { long nsOpenGLLayer = 0; long nsOpenGLLayerPFmt = 0; - int vsyncTimeout = 16; + float screenVSyncTimeout; // microSec + int vsyncTimeout; // microSec - for nsOpenGLLayer mode public boolean isNSContext() { return true; } - public long create(long share, int ctp, int major, int minor) { + public long create(long share, int ctp, int major, int minor) { long ctx = 0; final MacOSXCGLDrawable drawable = (MacOSXCGLDrawable) MacOSXCGLContext.this.drawable; final NativeSurface surface = drawable.getNativeSurface(); @@ -415,6 +416,8 @@ public abstract class MacOSXCGLContext extends GLContextImpl return 0; } config.setChosenPixelFormat(pixelFormat); + int sRefreshRate = CGL.getScreenRefreshRate(drawable.getNativeSurface().getGraphicsConfiguration().getScreen().getIndex()); + screenVSyncTimeout = 1000000f / (float)sRefreshRate; if(DEBUG) { System.err.println("NS create OSX>=lion "+isLionOrLater); System.err.println("NS create backendType: "+drawable.getOpenGLMode()); @@ -424,6 +427,7 @@ public abstract class MacOSXCGLContext extends GLContextImpl System.err.println("NS create pixelFormat: "+toHexString(pixelFormat)); System.err.println("NS create drawable native-handle: "+toHexString(drawable.getHandle())); System.err.println("NS create drawable NSView-handle: "+toHexString(drawable.getNSViewHandle())); + System.err.println("NS create screen refresh-rate: "+sRefreshRate+" hz, "+screenVSyncTimeout+" micros"); // Thread.dumpStack(); } try { @@ -483,6 +487,7 @@ public abstract class MacOSXCGLContext extends GLContextImpl System.err.println("NS create nsOpenGLLayer "+toHexString(nsOpenGLLayer)+", texSize "+texWidth+"x"+texHeight+", "+drawable); } backingLayerHost.attachSurfaceLayer(nsOpenGLLayer); + setSwapInterval(1); // enabled per default in layered surface } } finally { if(0!=pixelFormat) { @@ -554,23 +559,17 @@ public abstract class MacOSXCGLContext extends GLContextImpl public boolean setSwapInterval(int interval) { if(0 != nsOpenGLLayer) { CGL.setNSOpenGLLayerSwapInterval(nsOpenGLLayer, interval); + vsyncTimeout = interval * (int)screenVSyncTimeout; + if(DEBUG) { System.err.println("NS setSwapInterval: "+vsyncTimeout+" micros"); } } CGL.setSwapInterval(contextHandle, interval); - if (interval == 0) { - // v-sync is disabled, frames were drawn as quickly as possible without adding any - // timeout delay. - vsyncTimeout = 0; - } else { - // v-sync is enabled. Swaping interval of 1 means a - // timeout of 16ms -> 60Hz, 60fps - vsyncTimeout = interval * 16; - } return true; } public boolean swapBuffers() { - if(0 != nsOpenGLLayer && 0 < vsyncTimeout) { - // sync w/ CALayer renderer - wait until next frame is required (v-sync) + if( 0 != nsOpenGLLayer ) { + // If v-sync is disabled, frames will be drawn as quickly as possible + // w/o delay but in sync w/ CALayer. Otherwise wait until next swap interval (v-sync). CGL.waitUntilNSOpenGLLayerIsReady(nsOpenGLLayer, vsyncTimeout); } if(CGL.flushBuffer(contextHandle)) { diff --git a/src/jogl/native/macosx/MacOSXWindowSystemInterface-pbuffer.m b/src/jogl/native/macosx/MacOSXWindowSystemInterface-pbuffer.m index 6071f9610..66bd10f89 100644 --- a/src/jogl/native/macosx/MacOSXWindowSystemInterface-pbuffer.m +++ b/src/jogl/native/macosx/MacOSXWindowSystemInterface-pbuffer.m @@ -33,6 +33,8 @@ int texHeight; GLuint textureID; GLint swapInterval; + GLint swapIntervalCounter; + struct timespec lastWaitTime; #ifdef HAS_CADisplayLink CADisplayLink* displayLink; #else @@ -78,8 +80,14 @@ static CVReturn renderMyNSOpenGLLayer(CVDisplayLinkRef displayLink, #ifdef DBG_PERF [l tick]; #endif - pthread_cond_signal(&l->renderSignal); - SYNC_PRINT("-*-"); + if(0 < l->swapInterval) { + l->swapIntervalCounter++; + if(l->swapIntervalCounter>=l->swapInterval) { + l->swapIntervalCounter = 0; + pthread_cond_signal(&l->renderSignal); + SYNC_PRINT("S"); + } + } pthread_mutex_unlock(&l->renderLock); [pool release]; return kCVReturnSuccess; @@ -103,7 +111,9 @@ static CVReturn renderMyNSOpenGLLayer(CVDisplayLinkRef displayLink, pthread_cond_init(&renderSignal, NULL); // no attribute textureID = 0; - swapInterval = -1; + swapInterval = 1; // defaults to on (as w/ new GL profiles) + swapIntervalCounter = 0; + timespec_now(&lastWaitTime); shallDraw = NO; texWidth = _texWidth; texHeight = _texHeight; @@ -244,10 +254,10 @@ static CVReturn renderMyNSOpenGLLayer(CVDisplayLinkRef displayLink, pthread_mutex_lock(&renderLock); Bool res = NULL != pbuffer && YES == shallDraw; if(!res) { - SYNC_PRINT("<0>"); + SYNC_PRINT("0"); pthread_mutex_unlock(&renderLock); } else { - SYNC_PRINT("<"); + SYNC_PRINT("1"); } return res; } @@ -336,10 +346,10 @@ static CVReturn renderMyNSOpenGLLayer(CVDisplayLinkRef displayLink, [super drawInOpenGLContext: context pixelFormat: pixelFormat forLayerTime: timeInterval displayTime: timeStamp]; shallDraw = NO; if(0 >= swapInterval) { - pthread_cond_signal(&renderSignal); - SYNC_PRINT("*"); + pthread_cond_signal(&renderSignal); // just to wake up + SYNC_PRINT("s"); } - SYNC_PRINT("1>"); + SYNC_PRINT("$"); pthread_mutex_unlock(&renderLock); } @@ -352,6 +362,7 @@ static CVReturn renderMyNSOpenGLLayer(CVDisplayLinkRef displayLink, { DBG_PRINT("MyNSOpenGLLayer::setSwapInterval: %d\n", interval); swapInterval = interval; + swapIntervalCounter = 0; if(0 < swapInterval) { tc = 0; timespec_now(&t0); @@ -409,7 +420,7 @@ void setNSOpenGLLayerSwapInterval(NSOpenGLLayer* layer, int interval) { pthread_mutex_unlock(&l->renderLock); } -void waitUntilNSOpenGLLayerIsReady(NSOpenGLLayer* layer, long to_ms) { +void waitUntilNSOpenGLLayerIsReady(NSOpenGLLayer* layer, long to_micros) { MyNSOpenGLLayer* l = (MyNSOpenGLLayer*) layer; BOOL ready = NO; int wr = 0; @@ -420,17 +431,23 @@ void waitUntilNSOpenGLLayerIsReady(NSOpenGLLayer* layer, long to_ms) { ready = !l->shallDraw; } if(NO == ready) { - if(0 < to_ms) { - struct timespec to_abs; - timespec_now(&to_abs); - timespec_addms(&to_abs, to_ms); + if(0 < to_micros) { + #ifdef DBG_SYNC + struct timespec t0, t1, td, td2; + timespec_now(&t0); + #endif + struct timespec to_abs = l->lastWaitTime; + timespec_addmicros(&to_abs, to_micros); + #ifdef DBG_SYNC + timespec_subtract(&td, &to_abs, &t0); + fprintf(stderr, "(%ld) / ", timespec_milliseconds(&td)); + #endif wr = pthread_cond_timedwait(&l->renderSignal, &l->renderLock, &to_abs); #ifdef DBG_SYNC - struct timespec t1, td; timespec_now(&t1); - timespec_subtract(&td, &t1, &to_abs); - long td_ms = timespec_milliseconds(&td); - fprintf(stderr, "%ld ms", td_ms); + timespec_subtract(&td, &t1, &t0); + timespec_subtract(&td2, &t1, &l->lastWaitTime); + fprintf(stderr, "(%ld) / (%ld) ms", timespec_milliseconds(&td), timespec_milliseconds(&td2)); #endif } else { pthread_cond_wait (&l->renderSignal, &l->renderLock); @@ -439,32 +456,25 @@ void waitUntilNSOpenGLLayerIsReady(NSOpenGLLayer* layer, long to_ms) { } } while (NO == ready && 0 == wr) ; SYNC_PRINT("-%d}", ready); + timespec_now(&l->lastWaitTime); pthread_mutex_unlock(&l->renderLock); } void setNSOpenGLLayerNeedsDisplay(NSOpenGLLayer* layer) { - MyNSOpenGLLayer* l = (MyNSOpenGLLayer*) layer; - @synchronized(l) { - NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; - pthread_mutex_lock(&l->renderLock); - SYNC_PRINT("["); - l->shallDraw = YES; - if([l getSwapInterval] > 0) { - // only trigger update if async mode is off (swapInterval>0) - if ( [NSThread isMainThread] == YES ) { - [l setNeedsDisplay]; - } else { - // can't wait, otherwise we may deadlock AWT - [l performSelectorOnMainThread:@selector(setNeedsDisplay) withObject:nil waitUntilDone:NO]; - } - SYNC_PRINT("1]"); - } else { - SYNC_PRINT("0]"); - } - pthread_mutex_unlock(&l->renderLock); - // DBG_PRINT("MyNSOpenGLLayer::setNSOpenGLLayerNeedsDisplay %p\n", l); - [pool release]; - } + MyNSOpenGLLayer* l = (MyNSOpenGLLayer*) layer; + NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; + pthread_mutex_lock(&l->renderLock); + l->shallDraw = YES; + if ( [NSThread isMainThread] == YES ) { + [l setNeedsDisplay]; + } else { + // can't wait, otherwise we may deadlock AWT + [l performSelectorOnMainThread:@selector(setNeedsDisplay) withObject:nil waitUntilDone:NO]; + } + SYNC_PRINT("."); + pthread_mutex_unlock(&l->renderLock); + // DBG_PRINT("MyNSOpenGLLayer::setNSOpenGLLayerNeedsDisplay %p\n", l); + [pool release]; } void releaseNSOpenGLLayer(NSOpenGLLayer* layer) { diff --git a/src/jogl/native/macosx/MacOSXWindowSystemInterface.m b/src/jogl/native/macosx/MacOSXWindowSystemInterface.m index 979e57aee..8ac9f4700 100644 --- a/src/jogl/native/macosx/MacOSXWindowSystemInterface.m +++ b/src/jogl/native/macosx/MacOSXWindowSystemInterface.m @@ -745,3 +745,39 @@ Bool setGammaRamp(int tableSize, float* redRamp, float* greenRamp, float* blueRa void resetGammaRamp() { CGDisplayRestoreColorSyncSettings(); } + +/*** + * The following static functions are copied out of NEWT's OSX impl. <src/newt/native/MacWindow.m> + * May need to push code to NativeWindow, to remove duplication. + */ +static NSScreen * NewtScreen_getNSScreenByIndex(int screen_idx) { + NSArray *screens = [NSScreen screens]; + if(screen_idx<0) screen_idx=0; + if(screen_idx>=[screens count]) screen_idx=0; + return (NSScreen *) [screens objectAtIndex: screen_idx]; +} +static CGDirectDisplayID NewtScreen_getCGDirectDisplayIDByNSScreen(NSScreen *screen) { + // Mind: typedef uint32_t CGDirectDisplayID; - however, we assume it's 64bit on 64bit ?! + NSDictionary * dict = [screen deviceDescription]; + NSNumber * val = (NSNumber *) [dict objectForKey: @"NSScreenNumber"]; + // [NSNumber integerValue] returns NSInteger which is 32 or 64 bit native size + return (CGDirectDisplayID) [val integerValue]; +} +static long GetDictionaryLong(CFDictionaryRef theDict, const void* key) +{ + long value = 0; + CFNumberRef numRef; + numRef = (CFNumberRef)CFDictionaryGetValue(theDict, key); + if (numRef != NULL) + CFNumberGetValue(numRef, kCFNumberLongType, &value); + return value; +} +#define CGDDGetModeRefreshRate(mode) GetDictionaryLong((mode), kCGDisplayRefreshRate) + +int getScreenRefreshRate(int scrn_idx) { + NSScreen *screen = NewtScreen_getNSScreenByIndex(scrn_idx); + CGDirectDisplayID display = NewtScreen_getCGDirectDisplayIDByNSScreen(screen); + CFDictionaryRef mode = CGDisplayCurrentMode(display); + return CGDDGetModeRefreshRate(mode); +} + diff --git a/src/jogl/native/timespec.c b/src/jogl/native/timespec.c index 74c1a6901..50f0ca8c5 100644 --- a/src/jogl/native/timespec.c +++ b/src/jogl/native/timespec.c @@ -24,6 +24,20 @@ void timespec_addms(struct timespec *ts, long ms) ts->tv_nsec=ts->tv_nsec%1000000000; } +void timespec_addmicros(struct timespec *ts, long micro) +{ + int sec=micro/1000000; + micro=micro - sec*1000000; + + // perform the addition + ts->tv_nsec+=micro*1000; + + // adjust the time + ts->tv_sec+=ts->tv_nsec/1000000000 + sec; + ts->tv_nsec=ts->tv_nsec%1000000000; + +} + void timespec_addns(struct timespec *ts, long ns) { int sec=ns/1000000000; diff --git a/src/jogl/native/timespec.h b/src/jogl/native/timespec.h index 671eb4716..f900bfa16 100644 --- a/src/jogl/native/timespec.h +++ b/src/jogl/native/timespec.h @@ -5,6 +5,7 @@ void timespec_now(struct timespec *ts); void timespec_addms(struct timespec *ts, long ms); +void timespec_addmicros(struct timespec *ts, long micro); void timespec_addns(struct timespec *ts, long ns); /** returns 0: a==b, >0: a>b, <0: a<b */ diff --git a/src/test/com/jogamp/opengl/test/junit/jogl/demos/es2/awt/TestGearsES2AWT.java b/src/test/com/jogamp/opengl/test/junit/jogl/demos/es2/awt/TestGearsES2AWT.java index 5bf341388..c6e224548 100644 --- a/src/test/com/jogamp/opengl/test/junit/jogl/demos/es2/awt/TestGearsES2AWT.java +++ b/src/test/com/jogamp/opengl/test/junit/jogl/demos/es2/awt/TestGearsES2AWT.java @@ -38,9 +38,13 @@ import com.jogamp.newt.event.TraceKeyAdapter; import com.jogamp.newt.event.TraceWindowAdapter; import com.jogamp.opengl.test.junit.jogl.demos.es2.GearsES2; +import com.jogamp.opengl.test.junit.util.MiscUtils; import com.jogamp.opengl.test.junit.util.UITestCase; import com.jogamp.opengl.test.junit.util.QuitAdapter; import java.awt.Frame; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; import java.lang.reflect.InvocationTargetException; import org.junit.Assert; @@ -52,6 +56,9 @@ public class TestGearsES2AWT extends UITestCase { static int width, height; static boolean firstUIActionOnProcess = false; static boolean forceES2 = false; + static boolean shallUseOffscreenLayer = false; + static int swapInterval = 1; + static boolean showFPS = false; @BeforeClass public static void initClass() { @@ -69,10 +76,12 @@ public class TestGearsES2AWT extends UITestCase { final GLCanvas glCanvas = new GLCanvas(caps); Assert.assertNotNull(glCanvas); + glCanvas.setShallUseOffscreenLayer(shallUseOffscreenLayer); frame.add(glCanvas); frame.setSize(512, 512); + frame.setTitle("Gears AWT Test (translucent "+!caps.isBackgroundOpaque()+"), swapInterval "+swapInterval); - glCanvas.addGLEventListener(new GearsES2(1)); + glCanvas.addGLEventListener(new GearsES2(swapInterval)); Animator animator = new Animator(glCanvas); QuitAdapter quitAdapter = new QuitAdapter(); @@ -115,6 +124,8 @@ public class TestGearsES2AWT extends UITestCase { static long duration = 500; // ms public static void main(String args[]) { + boolean waitForKey = false; + for(int i=0; i<args.length; i++) { if(args[i].equals("-time")) { i++; @@ -123,11 +134,30 @@ public class TestGearsES2AWT extends UITestCase { } catch (Exception ex) { ex.printStackTrace(); } } else if(args[i].equals("-es2")) { forceES2 = true; + } else if(args[i].equals("-vsync")) { + i++; + swapInterval = MiscUtils.atoi(args[i], swapInterval); + } else if(args[i].equals("-layered")) { + shallUseOffscreenLayer = true; + } else if(args[i].equals("-showFPS")) { + showFPS = true; } else if(args[i].equals("-firstUIAction")) { firstUIActionOnProcess = true; + } else if(args[i].equals("-wait")) { + waitForKey = true; } } System.err.println("forceES2 "+forceES2); + System.err.println("swapInterval "+swapInterval); + System.err.println("shallUseOffscreenLayer "+shallUseOffscreenLayer); + + if(waitForKey) { + BufferedReader stdin = new BufferedReader(new InputStreamReader(System.in)); + System.err.println("Press enter to continue"); + try { + System.err.println(stdin.readLine()); + } catch (IOException e) { } + } org.junit.runner.JUnitCore.main(TestGearsES2AWT.class.getName()); } } diff --git a/src/test/com/jogamp/opengl/test/junit/jogl/demos/es2/newt/TestGearsES2NEWT.java b/src/test/com/jogamp/opengl/test/junit/jogl/demos/es2/newt/TestGearsES2NEWT.java index cab46b475..264b62fec 100644 --- a/src/test/com/jogamp/opengl/test/junit/jogl/demos/es2/newt/TestGearsES2NEWT.java +++ b/src/test/com/jogamp/opengl/test/junit/jogl/demos/es2/newt/TestGearsES2NEWT.java @@ -77,7 +77,7 @@ public class TestGearsES2NEWT extends UITestCase { static boolean alwaysOnTop = false; static boolean fullscreen = false; static boolean pmvUseBackingArray = true; - static boolean vsync = false; + static int swapInterval = 1; static boolean waitForKey = false; static boolean mouseVisible = true; static boolean mouseConfined = false; @@ -98,12 +98,12 @@ public class TestGearsES2NEWT extends UITestCase { } protected void runTestGL(GLCapabilitiesImmutable caps, boolean undecorated) throws InterruptedException { - System.err.println("requested: vsync "+vsync+", "+caps); + System.err.println("requested: vsync "+swapInterval+", "+caps); Display dpy = NewtFactory.createDisplay(null); Screen screen = NewtFactory.createScreen(dpy, screenIdx); final GLWindow glWindow = GLWindow.create(screen, caps); Assert.assertNotNull(glWindow); - glWindow.setTitle("Gears NEWT Test (translucent "+!caps.isBackgroundOpaque()+"), vsync "+vsync+", size "+wsize+", pos "+wpos); + glWindow.setTitle("Gears NEWT Test (translucent "+!caps.isBackgroundOpaque()+"), swapInterval "+swapInterval+", size "+wsize+", pos "+wpos); glWindow.setSize(wsize.getWidth(), wsize.getHeight()); if(null != wpos) { glWindow.setPosition(wpos.getX(), wpos.getY()); @@ -114,7 +114,7 @@ public class TestGearsES2NEWT extends UITestCase { glWindow.setPointerVisible(mouseVisible); glWindow.confinePointer(mouseConfined); - final GearsES2 demo = new GearsES2(vsync ? 1 : -1); + final GearsES2 demo = new GearsES2(swapInterval); demo.setPMVUseBackingArray(pmvUseBackingArray); glWindow.addGLEventListener(demo); if(waitForKey) { @@ -276,7 +276,8 @@ public class TestGearsES2NEWT extends UITestCase { } else if(args[i].equals("-pmvDirect")) { pmvUseBackingArray = false; } else if(args[i].equals("-vsync")) { - vsync = true; + i++; + swapInterval = MiscUtils.atoi(args[i], swapInterval); } else if(args[i].equals("-es2")) { forceES2 = true; } else if(args[i].equals("-wait")) { @@ -332,7 +333,7 @@ public class TestGearsES2NEWT extends UITestCase { System.err.println("atop "+alwaysOnTop); System.err.println("fullscreen "+fullscreen); System.err.println("pmvDirect "+(!pmvUseBackingArray)); - System.err.println("vsync "+vsync); + System.err.println("swapInterval "+swapInterval); System.err.println("mouseVisible "+mouseVisible); System.err.println("mouseConfined "+mouseConfined); System.err.println("loops "+loops); |