/**
* Copyright 2011 JogAmp Community. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, are
* permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this list of
* conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice, this list
* of conditions and the following disclaimer in the documentation and/or other materials
* provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY JogAmp Community ``AS IS'' AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JogAmp Community OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* The views and conclusions contained in the software and documentation are those of the
* authors and should not be interpreted as representing official policies, either expressed
* or implied, of JogAmp Community.
*/
package jogamp.opengl;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.ArrayList;
import com.jogamp.nativewindow.NativeWindowException;
import com.jogamp.opengl.GL2ES2;
import com.jogamp.opengl.GLDebugListener;
import com.jogamp.opengl.GLDebugMessage;
import com.jogamp.opengl.GLException;
import jogamp.common.os.PlatformPropsImpl;
import com.jogamp.common.ExceptionUtils;
import com.jogamp.common.os.Platform;
import com.jogamp.gluegen.runtime.ProcAddressTable;
import com.jogamp.opengl.GLExtensions;
/**
* The GLDebugMessageHandler, handling GL_ARB_debug_output or GL_AMD_debug_output
* debug messages.
*
*
An instance must be bound to the current thread's GLContext to achieve thread safety.
*
* A native callback function is registered at {@link #enable(boolean) enable(true)},
* which forwards received messages to the added {@link GLDebugListener} directly.
* Hence the {@link GLDebugListener#messageSent(GLDebugMessage)} implementation shall
* return as fast as possible.
*
* In case no GL_ARB_debug_output is available, but GL_AMD_debug_output,
* the messages are translated to ARB {@link GLDebugMessage}, using {@link GLDebugMessage#translateAMDEvent(com.jogamp.opengl.GLContext, long, int, int, int, String)}.
*/
public class GLDebugMessageHandler {
private static final boolean DEBUG = Debug.debug("GLDebugMessageHandler");
private static final int EXT_KHR = 1;
private static final int EXT_ARB = 2;
private static final int EXT_AMD = 3;
static {
if ( !initIDs0() ) {
throw new NativeWindowException("Failed to initialize GLDebugMessageHandler jmethodIDs");
}
}
private final GLContextImpl ctx;
private final ListenerSyncedImplStub listenerImpl;
// licefycle: init - EOL
private String extName;
private String extSuffix;
private int extType;
private long glDebugMessageCallbackProcAddress;
private boolean extAvailable;
private boolean synchronous;
// licefycle: enable - disable/EOL
private long handle;
/**
* @param ctx the associated GLContext
* @param glDebugExtension chosen extension to use
*/
public GLDebugMessageHandler(final GLContextImpl ctx) {
this.ctx = ctx;
this.listenerImpl = new ListenerSyncedImplStub();
this.glDebugMessageCallbackProcAddress = 0;
this.extName = null;
this.extSuffix = null;
this.extType = 0;
this.extAvailable = false;
this.handle = 0;
this.synchronous = true;
}
public void init(final boolean enable) {
if(DEBUG) {
System.err.println("GLDebugMessageHandler.init("+enable+")");
}
init();
if(isAvailable()) {
enableImpl(enable);
} else if(DEBUG) {
System.err.println("GLDebugMessageHandler.init("+enable+") .. n/a");
}
}
private final long getAddressFor(final ProcAddressTable table, final String functionName) {
return AccessController.doPrivileged(new PrivilegedAction() {
@Override
public Long run() {
try {
return Long.valueOf( table.getAddressFor(functionName) );
} catch (final IllegalArgumentException iae) {
return Long.valueOf(0);
}
}
} ).longValue();
}
public void init() {
ctx.validateCurrent();
if( isAvailable()) {
return;
}
if( !ctx.isGLDebugEnabled() ) {
if(DEBUG) {
System.err.println("GLDebugMessageHandler: GL DEBUG not set in ARB ctx options: "+ctx.getGLVersion());
}
return;
}
if(PlatformPropsImpl.OS_TYPE == Platform.OSType.WINDOWS && Platform.is32Bit()) {
// Currently buggy, ie. throws an exception after leaving the native callback.
// Probably a 32bit on 64bit JVM / OpenGL-driver issue.
if(DEBUG) {
System.err.println("GLDebugMessageHandler: Windows 32bit currently not supported!");
}
return;
}
if( ctx.isExtensionAvailable(GLExtensions.GL_KHR_debug) ) {
extName = GLExtensions.GL_KHR_debug;
extSuffix = ctx.isGLES() ? "KHR" : ""; // See SPEC!
extType = EXT_KHR;
} else if( ctx.isExtensionAvailable(GLExtensions.ARB_debug_output) ) {
extName = GLExtensions.ARB_debug_output;
extSuffix = "ARB";
extType = EXT_ARB;
} else if( ctx.isExtensionAvailable(GLExtensions.AMD_debug_output) ) {
extName = GLExtensions.AMD_debug_output;
extSuffix = "AMD";
extType = EXT_AMD;
}
// Validate GL Profile, just to be sure
switch(extType) {
case EXT_KHR:
if( !ctx.isGL2ES2() ) {
if(DEBUG) {
System.err.println("Non GL2ES2 context not supported, has "+ctx.getGLVersion());
}
extType = 0;
}
break;
case EXT_ARB:
// fall through intended
case EXT_AMD:
if( !ctx.isGL2GL3() ) {
if(DEBUG) {
System.err.println("Non GL2GL3 context not supported, has "+ctx.getGLVersion());
}
extType = 0;
}
break;
}
if(0 == extType) {
extName = null;
extSuffix = null;
if(DEBUG) {
System.err.println("GLDebugMessageHandler: No extension available! "+ctx.getGLVersion());
System.err.println("GL_EXTENSIONS "+ctx.getGLExtensionCount());
System.err.println(ctx.getGLExtensionsString());
}
return;
} else if(DEBUG) {
System.err.println("GLDebugMessageHandler: Using extension: <"+extName+"> with suffix <"+extSuffix+">");
}
final ProcAddressTable procAddressTable = ctx.getGLProcAddressTable();
switch(extType) {
case EXT_KHR:
glDebugMessageCallbackProcAddress = getAddressFor(procAddressTable, "glDebugMessageCallback"+extSuffix);
break;
case EXT_ARB:
glDebugMessageCallbackProcAddress = getAddressFor(procAddressTable, "glDebugMessageCallback"+extSuffix);
break;
case EXT_AMD:
glDebugMessageCallbackProcAddress = getAddressFor(procAddressTable, "glDebugMessageCallback"+extSuffix);
break;
}
extAvailable = 0 < extType && null != extName && null != extSuffix && 0 != glDebugMessageCallbackProcAddress;
if(DEBUG) {
System.err.println("GLDebugMessageHandler: extAvailable: "+extAvailable+", glDebugMessageCallback* : 0x"+Long.toHexString(glDebugMessageCallbackProcAddress));
}
if(!extAvailable) {
glDebugMessageCallbackProcAddress = 0;
}
handle = 0;
}
public final boolean isAvailable() { return extAvailable; }
/**
* @return The extension implementing the GLDebugMessage feature,
* either {@link #GL_ARB_debug_output} or {@link #GL_AMD_debug_output}.
* If unavailable null is returned.
*/
public final String getExtension() {
return extName;
}
public final boolean isExtensionKHRARB() {
return EXT_KHR == extType || EXT_ARB == extType;
}
public final boolean isExtensionKHR() {
return EXT_KHR == extType;
}
public final boolean isExtensionARB() {
return EXT_ARB == extType;
}
public final boolean isExtensionAMD() {
return EXT_AMD == extType;
}
/**
* @see com.jogamp.opengl.GLContext#isGLDebugSynchronous()
*/
public final boolean isSynchronous() { return synchronous; }
/**
* @see com.jogamp.opengl.GLContext#setGLDebugSynchronous(boolean)
*/
public final void setSynchronous(final boolean synchronous) {
this.synchronous = synchronous;
if( isEnabled() ) {
setSynchronousImpl();
}
}
private final void setSynchronousImpl() {
if(isExtensionKHRARB()) {
if(synchronous) {
ctx.getGL().glEnable(GL2ES2.GL_DEBUG_OUTPUT_SYNCHRONOUS);
} else {
ctx.getGL().glDisable(GL2ES2.GL_DEBUG_OUTPUT_SYNCHRONOUS);
}
if(DEBUG) {
System.err.println("GLDebugMessageHandler: synchronous "+synchronous);
}
}
}
/**
* @see com.jogamp.opengl.GLContext#enableGLDebugMessage(boolean)
*/
public final void enable(final boolean enable) throws GLException {
ctx.validateCurrent();
if(!isAvailable()) {
return;
}
enableImpl(enable);
}
final void enableImpl(final boolean enable) throws GLException {
if(enable) {
if(0 == handle) {
setSynchronousImpl();
handle = register0(glDebugMessageCallbackProcAddress, extType);
if(0 == handle) {
throw new GLException("Failed to register via \"glDebugMessageCallback*\" using "+extName);
}
}
} else {
if(0 != handle) {
unregister0(glDebugMessageCallbackProcAddress, handle);
handle = 0;
}
}
if(DEBUG) {
System.err.println("GLDebugMessageHandler: enable("+enable+") -> 0x" + Long.toHexString(handle));
}
}
public final boolean isEnabled() { return 0 != handle; }
public final int listenerSize() {
return listenerImpl.size();
}
public final void addListener(final GLDebugListener listener) {
listenerImpl.addListener(-1, listener);
}
public final void addListener(final int index, final GLDebugListener listener) {
listenerImpl.addListener(index, listener);
}
public final void removeListener(final GLDebugListener listener) {
listenerImpl.removeListener(listener);
}
private final void sendMessage(final GLDebugMessage msg) {
synchronized(listenerImpl) {
if(DEBUG) {
System.err.println("GLDebugMessageHandler: "+msg);
}
final ArrayList listeners = listenerImpl.getListeners();
for(int i=0; i java
//
protected final void glDebugMessageARB(final int source, final int type, final int id, final int severity, final String msg) {
final GLDebugMessage event = new GLDebugMessage(ctx, System.currentTimeMillis(), source, type, id, severity, msg);
sendMessage(event);
}
protected final void glDebugMessageAMD(final int id, final int category, final int severity, final String msg) {
final GLDebugMessage event = GLDebugMessage.translateAMDEvent(ctx, System.currentTimeMillis(), id, category, severity, msg);
sendMessage(event);
}
//
// java -> native
//
private static native boolean initIDs0();
private native long register0(long glDebugMessageCallbackProcAddress, int extType);
private native void unregister0(long glDebugMessageCallbackProcAddress, long handle);
}