/*
 * Copyright (c) 2003 Sun Microsystems, Inc. All Rights Reserved.
 * Copyright (c) 2010 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:
 *
 * - Redistribution of source code must retain the above copyright
 *   notice, this list of conditions and the following disclaimer.
 *
 * - Redistribution 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.
 *
 * Neither the name of Sun Microsystems, Inc. or the names of
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * This software is provided "AS IS," without a warranty of any kind. ALL
 * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES,
 * INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A
 * PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN
 * MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL NOT BE LIABLE FOR
 * ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR
 * DISTRIBUTING THIS SOFTWARE OR ITS DERIVATIVES. IN NO EVENT WILL SUN OR
 * ITS LICENSORS BE LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR
 * DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE
 * DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY OF LIABILITY,
 * ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE, EVEN IF
 * SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
 *
 * You acknowledge that this software is not designed or intended for use
 * in the design, construction, operation or maintenance of any nuclear
 * facility.
 *
 * Sun gratefully acknowledges that this software was originally authored
 * and developed by Kenneth Bradley Russell and Christopher John Kline.
 */

package jogamp.opengl;

import java.lang.reflect.Method;
import java.nio.ByteBuffer;
import java.nio.IntBuffer;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.HashMap;
import java.util.Map;

import com.jogamp.common.os.DynamicLookupHelper;
import com.jogamp.common.os.Platform;
import com.jogamp.common.util.ReflectionUtil;
import com.jogamp.common.util.VersionNumber;
import com.jogamp.common.util.VersionNumberString;
import com.jogamp.common.util.locks.RecursiveLock;
import com.jogamp.gluegen.runtime.FunctionAddressResolver;
import com.jogamp.gluegen.runtime.ProcAddressTable;
import com.jogamp.gluegen.runtime.opengl.GLNameResolver;
import com.jogamp.gluegen.runtime.opengl.GLProcAddressResolver;
import com.jogamp.opengl.GLExtensions;
import com.jogamp.opengl.GLRendererQuirks;

import javax.media.nativewindow.AbstractGraphicsConfiguration;
import javax.media.nativewindow.AbstractGraphicsDevice;
import javax.media.nativewindow.NativeSurface;
import javax.media.nativewindow.NativeWindowFactory;
import javax.media.opengl.GL;
import javax.media.opengl.GL2ES2;
import javax.media.opengl.GL2ES3;
import javax.media.opengl.GL2GL3;
import javax.media.opengl.GLCapabilitiesImmutable;
import javax.media.opengl.GLContext;
import javax.media.opengl.GLDebugListener;
import javax.media.opengl.GLDebugMessage;
import javax.media.opengl.GLDrawable;
import javax.media.opengl.GLDrawableFactory;
import javax.media.opengl.GLException;
import javax.media.opengl.GLPipelineFactory;
import javax.media.opengl.GLProfile;

public abstract class GLContextImpl extends GLContext {
  /**
   * Context full qualified name: display_type + display_connection + major + minor + ctp.
   * This is the key for all cached GL ProcAddressTables, etc, to support multi display/device setups.
   */
  private String contextFQN;

  private int additionalCtxCreationFlags;

  // Cache of the functions that are available to be called at the current
  // moment in time
  protected ExtensionAvailabilityCache extensionAvailability;
  // Table that holds the addresses of the native C-language entry points for
  // OpenGL functions.
  private ProcAddressTable glProcAddressTable;

  private String glVendor;
  private String glRenderer;
  private String glRendererLowerCase;
  private String glVersion;

  // Tracks creation and initialization of buffer objects to avoid
  // repeated glGet calls upon glMapBuffer operations
  private GLBufferSizeTracker bufferSizeTracker; // Singleton - Set by GLContextShareSet
  private final GLBufferStateTracker bufferStateTracker = new GLBufferStateTracker();
  private final GLStateTracker glStateTracker = new GLStateTracker();
  private GLDebugMessageHandler glDebugHandler = null;
  private final int[] boundFBOTarget = new int[] { 0, 0 }; // { draw, read }
  private int defaultVAO = 0;

  protected GLDrawableImpl drawable;
  protected GLDrawableImpl drawableRead;

  private volatile boolean pixelDataEvaluated;
  private int /* pixelDataInternalFormat, */ pixelDataFormat, pixelDataType;

  protected GL gl;

  protected static final Object mappedContextTypeObjectLock;
  protected static final HashMap<String, ExtensionAvailabilityCache> mappedExtensionAvailabilityCache;
  protected static final HashMap<String, ProcAddressTable> mappedGLProcAddress;
  protected static final HashMap<String, ProcAddressTable> mappedGLXProcAddress;

  static {
      mappedContextTypeObjectLock = new Object();
      mappedExtensionAvailabilityCache = new HashMap<String, ExtensionAvailabilityCache>();
      mappedGLProcAddress = new HashMap<String, ProcAddressTable>();
      mappedGLXProcAddress = new HashMap<String, ProcAddressTable>();
  }

  public static void shutdownImpl() {
      mappedExtensionAvailabilityCache.clear();
      mappedGLProcAddress.clear();
      mappedGLXProcAddress.clear();
  }

  public GLContextImpl(GLDrawableImpl drawable, GLContext shareWith) {
    super();

    if (shareWith != null) {
      GLContextShareSet.registerSharing(this, shareWith);
    }
    GLContextShareSet.synchronizeBufferObjectSharing(shareWith, this);

    this.drawable = drawable;
    this.drawableRead = drawable;

    this.glDebugHandler = new GLDebugMessageHandler(this);
  }

  private final void clearStates() {
      // Because we don't know how many other contexts we might be
      // sharing with (and it seems too complicated to implement the
      // GLObjectTracker's ref/unref scheme for the buffer-related
      // optimizations), simply clear the cache of known buffers' sizes
      // when we destroy contexts
      if (bufferSizeTracker != null) {
          bufferSizeTracker.clearCachedBufferSizes();
      }
      bufferStateTracker.clearBufferObjectState();
      glStateTracker.setEnabled(false);
      glStateTracker.clearStates();
  }

  @Override
  protected void resetStates(boolean isInit) {
      if( !isInit ) {
          clearStates();
      }
      extensionAvailability = null;
      glProcAddressTable = null;
      gl = null;
      contextFQN = null;
      additionalCtxCreationFlags = 0;

      glVendor = "";
      glRenderer = glVendor;
      glRendererLowerCase = glRenderer;
      glVersion = glVendor;

      if (boundFBOTarget != null) { // <init>
          boundFBOTarget[0] = 0; // draw
          boundFBOTarget[1] = 0; // read
      }

      pixelDataEvaluated = false;

      super.resetStates(isInit);
  }

  @Override
  public final GLDrawable setGLReadDrawable(GLDrawable read) {
    if(!isGLReadDrawableAvailable()) {
        throw new GLException("Setting read drawable feature not available");
    }
    final boolean lockHeld = lock.isOwner(Thread.currentThread());
    if(lockHeld) {
        release();
    } else if(lock.isLockedByOtherThread()) { // still could glitch ..
        throw new GLException("GLContext current by other thread ("+lock.getOwner()+"), operation not allowed.");
    }
    final GLDrawable old = drawableRead;
    drawableRead = ( null != read ) ? (GLDrawableImpl) read : drawable;
    if(lockHeld) {
        makeCurrent();
    }
    return old;
  }

  @Override
  public final GLDrawable getGLReadDrawable() {
    return drawableRead;
  }

  @Override
  public final GLDrawable setGLDrawable(GLDrawable readWrite, boolean setWriteOnly) {
    if( drawable == readWrite && ( setWriteOnly || drawableRead == readWrite ) ) {
        return drawable; // no change.
    }
    final Thread currentThread = Thread.currentThread();
    if( lock.isLockedByOtherThread() ) {
        throw new GLException("GLContext current by other thread "+lock.getOwner().getName()+", operation not allowed on this thread "+currentThread.getName());
    }
    final boolean lockHeld = lock.isOwner(currentThread);
    if( lockHeld && lock.getHoldCount() > 1 ) {
        // would need to makeCurrent * holdCount
        throw new GLException("GLContext is recursively locked - unsupported for setGLDrawable(..)");
    }
    final GLDrawableImpl old = drawable;
    if( isCreated() && null != old && old.isRealized() ) {
        if(!lockHeld) {
            makeCurrent();
        }
        associateDrawable(false);
        if(!lockHeld) {
            release();
        }
    }
    if(lockHeld) {
        release();
    }
    if( !setWriteOnly || drawableRead == drawable ) { // if !setWriteOnly || !explicitReadDrawable
        drawableRead = (GLDrawableImpl) readWrite;
    }
    drawableRetargeted |= null != drawable && readWrite != drawable;
    drawable = (GLDrawableImpl) readWrite ;
    if( isCreated() && null != drawable && drawable.isRealized() ) {
        makeCurrent(true); // implicit: associateDrawable(true)
        if( !lockHeld ) {
            release();
        }
    }
    return old;
  }

  @Override
  public final GLDrawable getGLDrawable() {
    return drawable;
  }

  public final GLDrawableImpl getDrawableImpl() {
    return (GLDrawableImpl) getGLDrawable();
  }

  @Override
  public final GL getRootGL() {
      GL _gl = gl;
      GL _parent = _gl.getDownstreamGL();
      while ( null != _parent ) {
          _gl = _parent;
          _parent = _gl.getDownstreamGL();
      }
      return _gl;
  }

  @Override
  public final GL getGL() {
    return gl;
  }

  @Override
  public GL setGL(GL gl) {
    if( DEBUG ) {
        final String sgl1 = (null!=this.gl)?this.gl.getClass().getSimpleName()+", "+this.gl.toString():"<null>";
        final String sgl2 = (null!=gl)?gl.getClass().getSimpleName()+", "+gl.toString():"<null>";
        System.err.println("Info: setGL (OpenGL "+getGLVersion()+"): "+getThreadName()+", "+sgl1+" -> "+sgl2);
        Thread.dumpStack();
    }
    this.gl = gl;
    return gl;
  }

  @Override
  public final int getDefaultVAO() {
      return defaultVAO;
  }

  /**
   * Call this method to notify the OpenGL context
   * that the drawable has changed (size or position).
   *
   * <p>
   * This is currently being used and overridden by Mac OSX,
   * which issues the {@link jogamp.opengl.macosx.cgl.CGL#updateContext(long) NSOpenGLContext update()} call.
   * </p>
   *
   * @throws GLException
   */
  protected void drawableUpdatedNotify() throws GLException { }

  public abstract Object getPlatformGLExtensions();

  // Note: the surface is locked within [makeCurrent .. swap .. release]
  @Override
  public void release() throws GLException {
    release(false);
  }
  private void release(boolean inDestruction) throws GLException {
    if( TRACE_SWITCH ) {
        System.err.println(getThreadName() +": GLContext.ContextSwitch[release.0]: obj " + toHexString(hashCode()) + ", ctx "+toHexString(contextHandle)+", surf "+toHexString(drawable.getHandle())+", inDestruction: "+inDestruction+", "+lock);
    }
    if ( !lock.isOwner(Thread.currentThread()) ) {
        final String msg = getThreadName() +": Context not current on thread, obj " + toHexString(hashCode())+", ctx "+toHexString(contextHandle)+", surf "+toHexString(drawable.getHandle())+", inDestruction: "+inDestruction+", "+lock;
        if( DEBUG_TRACE_SWITCH ) {
            System.err.println(msg);
            if( null != lastCtxReleaseStack ) {
                System.err.print("Last release call: ");
                lastCtxReleaseStack.printStackTrace();
            } else {
                System.err.println("Last release call: NONE");
            }
        }
        throw new GLException(msg);
    }

    Throwable drawableContextMadeCurrentException = null;
    final boolean actualRelease = ( inDestruction || lock.getHoldCount() == 1 ) && 0 != contextHandle;
    try {
        if( actualRelease ) {
            if( !inDestruction ) {
                try {
                    contextMadeCurrent(false);
                } catch (Throwable t) {
                    drawableContextMadeCurrentException = t;
                }
            }
            releaseImpl();
        }
    } finally {
      // exception prone ..
      if( actualRelease ) {
          setCurrent(null);
      }
      drawable.unlockSurface();
      lock.unlock();
      if( DEBUG_TRACE_SWITCH ) {
          final String msg = getThreadName() +": GLContext.ContextSwitch[release.X]: obj " + toHexString(hashCode()) + ", ctx "+toHexString(contextHandle)+", surf "+toHexString(drawable.getHandle())+" - "+(actualRelease?"switch":"keep  ")+" - "+lock;
          lastCtxReleaseStack = new Throwable(msg);
          if( TRACE_SWITCH ) {
              System.err.println(msg);
              // Thread.dumpStack();
          }
      }
    }
    if(null != drawableContextMadeCurrentException) {
      throw new GLException("GLContext.release(false) during GLDrawableImpl.contextMadeCurrent(this, false)", drawableContextMadeCurrentException);
    }

  }
  private Throwable lastCtxReleaseStack = null;
  protected abstract void releaseImpl() throws GLException;

  @Override
  public final void destroy() {
      if ( DEBUG_TRACE_SWITCH ) {
          final long drawH = null != drawable ? drawable.getHandle() : 0;
          System.err.println(getThreadName() + ": GLContextImpl.destroy.0: obj " + toHexString(hashCode()) + ", ctx " + toHexString(contextHandle) +
                  ", surf "+toHexString(drawH)+", isShared "+GLContextShareSet.isShared(this)+" - "+lock);
      }
      if ( 0 != contextHandle ) { // isCreated() ?
          if ( null == drawable ) {
              throw new GLException("GLContext created but drawable is null: "+toString());
          }
          final int lockRes = drawable.lockSurface();
          if ( NativeSurface.LOCK_SURFACE_NOT_READY >= lockRes ) {
                // this would be odd ..
                throw new GLException("Surface not ready to lock: "+drawable);
          }
          Throwable associateDrawableException = null;
          try {
              if ( !drawable.isRealized() ) {
                  throw new GLException("GLContext created but drawable not realized: "+toString());
              }
              // Must hold the lock around the destroy operation to make sure we
              // don't destroy the context while another thread renders to it.
              lock.lock(); // holdCount++ -> 1 - n (1: not locked, 2-n: destroy while rendering)
              if ( lock.getHoldCount() > 2 ) {
                  final String msg = getThreadName() + ": GLContextImpl.destroy: obj " + toHexString(hashCode()) + ", ctx " + toHexString(contextHandle);
                  if ( DEBUG_TRACE_SWITCH ) {
                      System.err.println(msg+" - Lock was hold more than once - makeCurrent/release imbalance: "+lock);
                      Thread.dumpStack();
                  }
              }
              try {
                  // if not current, makeCurrent(), to call associateDrawable(..) and to disable debug handler
                  if ( lock.getHoldCount() == 1 ) {
                      if ( GLContext.CONTEXT_NOT_CURRENT == makeCurrent() ) {
                          throw new GLException("GLContext.makeCurrent() failed: "+toString());
                      }
                  }
                  try {
                      associateDrawable(false);
                  } catch (Throwable t) {
                      associateDrawableException = t;
                  }
                  if ( 0 != defaultVAO ) {
                      final int[] tmp = new int[] { defaultVAO };
                      final GL2ES3 gl2es3 = gl.getRootGL().getGL2ES3();
                      gl2es3.glBindVertexArray(0);
                      gl2es3.glDeleteVertexArrays(1, tmp, 0);
                      defaultVAO = 0;
                  }
                  glDebugHandler.enable(false);
                  if(lock.getHoldCount() > 1) {
                      // pending release() after makeCurrent()
                      release(true);
                  }
                  destroyImpl();
                  contextHandle = 0;
                  glDebugHandler = null;
                  // this maybe impl. in a platform specific way to release remaining shared ctx.
                  if( GLContextShareSet.contextDestroyed(this) && !GLContextShareSet.hasCreatedSharedLeft(this) ) {
                      GLContextShareSet.unregisterSharing(this);
                  }
                  resetStates(false);
              } finally {
                  lock.unlock();
                  if ( DEBUG_TRACE_SWITCH ) {
                      System.err.println(getThreadName() + ": GLContextImpl.destroy.X: obj " + toHexString(hashCode()) + ", ctx " + toHexString(contextHandle) +
                              ", isShared "+GLContextShareSet.isShared(this)+" - "+lock);
                  }
              }
          } finally {
              drawable.unlockSurface();
          }
          if( null != associateDrawableException ) {
              throw new GLException("Exception @ destroy's associateDrawable(false)", associateDrawableException);
          }
      } else {
          resetStates(false);
      }
  }
  protected abstract void destroyImpl() throws GLException;

  @Override
  public final void copy(GLContext source, int mask) throws GLException {
    if (source.getHandle() == 0) {
      throw new GLException("Source OpenGL context has not been created");
    }
    if (getHandle() == 0) {
      throw new GLException("Destination OpenGL context has not been created");
    }

    final int lockRes = drawable.lockSurface();
    if (NativeSurface.LOCK_SURFACE_NOT_READY >= lockRes) {
        // this would be odd ..
        throw new GLException("Surface not ready to lock");
    }
    try {
        copyImpl(source, mask);
    } finally {
      drawable.unlockSurface();
    }
  }
  protected abstract void copyImpl(GLContext source, int mask) throws GLException;

  //----------------------------------------------------------------------
  //

  /**
   * {@inheritDoc}
   * <p>
   * MakeCurrent functionality, which also issues the creation of the actual OpenGL context.
   * </p>
   * The complete callgraph for general OpenGL context creation is:<br>
   * <ul>
   *    <li> {@link #makeCurrent} <i>GLContextImpl</i></li>
   *    <li> {@link #makeCurrentImpl} <i>Platform Implementation</i></li>
   *    <li> {@link #create} <i>Platform Implementation</i></li>
   *    <li> If <code>ARB_create_context</code> is supported:
   *    <ul>
   *        <li> {@link #createContextARB} <i>GLContextImpl</i></li>
   *        <li> {@link #createContextARBImpl} <i>Platform Implementation</i></li>
   *    </ul></li>
   * </ul><br>
   *
   * Once at startup, ie triggered by the singleton constructor of a {@link GLDrawableFactoryImpl} specialization,
   * calling {@link #createContextARB} will query all available OpenGL versions:<br>
   * <ul>
   *    <li> <code>FOR ALL GL* DO</code>:
   *    <ul>
   *        <li> {@link #createContextARBMapVersionsAvailable}
   *        <ul>
   *            <li> {@link #createContextARBVersions}</li>
   *        </ul></li>
   *        <li> {@link #mapVersionAvailable}</li>
   *    </ul></li>
   * </ul><br>
   *
   * @see #makeCurrentImpl
   * @see #create
   * @see #createContextARB
   * @see #createContextARBImpl
   * @see #mapVersionAvailable
   * @see #destroyContextARBImpl
   */
  @Override
  public final int makeCurrent() throws GLException {
      return makeCurrent(false);
  }

  protected final int makeCurrent(boolean forceDrawableAssociation) throws GLException {
    if( TRACE_SWITCH ) {
        System.err.println(getThreadName() +": GLContext.ContextSwitch[makeCurrent.0]: obj " + toHexString(hashCode()) + ", ctx "+toHexString(contextHandle)+", surf "+toHexString(drawable.getHandle())+" - "+lock);
    }

    // Note: the surface is locked within [makeCurrent .. swap .. release]
    final int lockRes = drawable.lockSurface();
    if (NativeSurface.LOCK_SURFACE_NOT_READY >= lockRes) {
        if( DEBUG_TRACE_SWITCH ) {
            System.err.println(getThreadName() +": GLContext.ContextSwitch[makeCurrent.X1]: obj " + toHexString(hashCode()) + ", ctx "+toHexString(contextHandle)+", surf "+toHexString(drawable.getHandle())+" - Surface Not Ready - CONTEXT_NOT_CURRENT - "+lock);
        }
        return CONTEXT_NOT_CURRENT;
    }

    boolean unlockResources = true; // Must be cleared if successful, otherwise finally block will release context and/or surface!
    int res = CONTEXT_NOT_CURRENT;
    try {
        if ( drawable.isRealized() ) {
            if ( 0 == drawable.getHandle() ) {
                throw new GLException("drawable has invalid handle: "+drawable);
            }
            lock.lock();
            try {
                // One context can only be current by one thread,
                // and one thread can only have one context current!
                final GLContext current = getCurrent();
                if (current != null) {
                    if (current == this) { // implicit recursive locking!
                        // Assume we don't need to make this context current again
                        // For Mac OS X, however, we need to update the context to track resizes
                        drawableUpdatedNotify();
                        unlockResources = false; // success
                        if( TRACE_SWITCH ) {
                            System.err.println(getThreadName() +": GLContext.ContextSwitch[makeCurrent.X2]: obj " + toHexString(hashCode()) + ", ctx "+toHexString(contextHandle)+", surf "+toHexString(drawable.getHandle())+" - keep   - CONTEXT_CURRENT - "+lock);
                        }
                        return CONTEXT_CURRENT;
                    } else {
                        current.release();
                    }
                }
                res = makeCurrentWithinLock(lockRes);
                unlockResources = CONTEXT_NOT_CURRENT == res; // success ?

                /**
                 * FIXME: refactor dependence on Java 2D / JOGL bridge
                    if ( tracker != null && res == CONTEXT_CURRENT_NEW ) {
                        // Increase reference count of GLObjectTracker
                        tracker.ref();
                    }
                 */
            } catch (RuntimeException e) {
              unlockResources = true;
              throw e;
            } finally {
              if (unlockResources) {
                if( DEBUG_TRACE_SWITCH ) {
                  System.err.println(getThreadName() +": GLContext.ContextSwitch[makeCurrent.1]: Context lock.unlock() due to error, res "+makeCurrentResultToString(res)+", "+lock);
                }
                lock.unlock();
              }
            }
        } /* if ( drawable.isRealized() ) */
    } catch (RuntimeException e) {
      unlockResources = true;
      throw e;
    } finally {
      if (unlockResources) {
        drawable.unlockSurface();
      }
    }

    if (res != CONTEXT_NOT_CURRENT) {
      setCurrent(this);
      if(res == CONTEXT_CURRENT_NEW) {
        // check if the drawable's and the GL's GLProfile are equal
        // throws an GLException if not
        getGLDrawable().getGLProfile().verifyEquality(gl.getGLProfile());

        glDebugHandler.init( isGL2GL3() && isGLDebugEnabled() );

        if(DEBUG_GL) {
            setGL( GLPipelineFactory.create("javax.media.opengl.Debug", null, gl, null) );
            if(glDebugHandler.isEnabled()) {
                glDebugHandler.addListener(new GLDebugMessageHandler.StdErrGLDebugListener(true));
            }
        }
        if(TRACE_GL) {
            setGL( GLPipelineFactory.create("javax.media.opengl.Trace", null, gl, new Object[] { System.err } ) );
        }

        forceDrawableAssociation = true;
      }

      if( forceDrawableAssociation ) {
          associateDrawable(true);
      }

      contextMadeCurrent(true);

      /* FIXME: refactor dependence on Java 2D / JOGL bridge

      // Try cleaning up any stale server-side OpenGL objects
      // FIXME: not sure what to do here if this throws
      if (deletedObjectTracker != null) {
        deletedObjectTracker.clean(getGL());
      }
      */
    }
    if( TRACE_SWITCH ) {
        System.err.println(getThreadName() +": GLContext.ContextSwitch[makeCurrent.X3]: obj " + toHexString(hashCode()) + ", ctx "+toHexString(contextHandle)+", surf "+toHexString(drawable.getHandle())+" - switch - "+makeCurrentResultToString(res)+" - stateTracker.on "+glStateTracker.isEnabled()+" - "+lock);
    }
    return res;
  }

  private final int makeCurrentWithinLock(int surfaceLockRes) throws GLException {
      if (!isCreated()) {
        if( 0 >= drawable.getWidth() || 0 >= drawable.getHeight() ) {
            if ( DEBUG_TRACE_SWITCH ) {
                System.err.println(getThreadName() + ": Create GL context REJECTED (zero surface size) obj " + toHexString(hashCode()) + ", surf "+toHexString(drawable.getHandle())+" for " + getClass().getName());
                System.err.println(drawable.toString());
            }
            return CONTEXT_NOT_CURRENT;
        }
        if(DEBUG_GL) {
            // only impacts w/ createContextARB(..)
            additionalCtxCreationFlags |= GLContext.CTX_OPTION_DEBUG ;
        }

        final GLContextImpl shareWith = (GLContextImpl) GLContextShareSet.getCreatedShare(this);
        final long shareWithHandle;
        if (null != shareWith) {
            shareWith.getDrawableImpl().lockSurface();
            shareWithHandle = shareWith.getHandle();
            if (0 == shareWithHandle) {
                throw new GLException("GLContextShareSet returned an invalid OpenGL context: "+this);
            }
        } else {
            shareWithHandle = 0;
        }
        final boolean created;
        try {
            created = createImpl(shareWithHandle); // may throws exception if fails
            if( created && hasNoDefaultVAO() ) {
                final int[] tmp = new int[1];
                final GL rootGL = gl.getRootGL();
                if( rootGL.isGL2ES3() ) { // FIXME remove if ES2 == ES3 later
                    final GL2ES3 gl2es3 = rootGL.getGL2ES3();
                    gl2es3.glGenVertexArrays(1, tmp, 0);
                    defaultVAO = tmp[0];
                    gl2es3.glBindVertexArray(defaultVAO);
                }
            }
        } finally {
            if (null != shareWith) {
                shareWith.getDrawableImpl().unlockSurface();
            }
        }
        if ( DEBUG_TRACE_SWITCH ) {
            if(created) {
                System.err.println(getThreadName() + ": Create GL context OK: obj " + toHexString(hashCode()) + ", ctx " + toHexString(contextHandle) + ", surf "+toHexString(drawable.getHandle())+" for " + getClass().getName()+" - "+getGLVersion());
                // Thread.dumpStack();
            } else {
                System.err.println(getThreadName() + ": Create GL context FAILED obj " + toHexString(hashCode()) + ", surf "+toHexString(drawable.getHandle())+" for " + getClass().getName());
            }
        }
        if(!created) {
            return CONTEXT_NOT_CURRENT;
        }

        // finalize mapping the available GLVersions, in case it's not done yet
        {
            final AbstractGraphicsConfiguration config = drawable.getNativeSurface().getGraphicsConfiguration();
            final AbstractGraphicsDevice device = config.getScreen().getDevice();

            // Non ARB desktop profiles may not have been registered
            if( !GLContext.getAvailableGLVersionsSet(device) ) {        // not yet set
                if( 0 == ( ctxOptions & GLContext.CTX_PROFILE_ES) ) {   // not ES profile
                    final int reqMajor;
                    final int reqProfile;
                    if( ctxVersion.compareTo(Version300) <= 0 ) {
                        reqMajor = 2;
                    } else {
                        reqMajor = ctxVersion.getMajor();
                    }
                    if( 0 != ( ctxOptions & GLContext.CTX_PROFILE_CORE) ) {
                        reqProfile = GLContext.CTX_PROFILE_CORE;
                    } else {
                        reqProfile = GLContext.CTX_PROFILE_COMPAT;
                    }
                    GLContext.mapAvailableGLVersion(device, reqMajor, reqProfile,
                                                    ctxVersion.getMajor(), ctxVersion.getMinor(), ctxOptions);
                    GLContext.setAvailableGLVersionsSet(device);

                    if (DEBUG) {
                      System.err.println(getThreadName() + ": createContextOLD-MapVersionsAvailable HAVE: " + device+" -> "+reqMajor+"."+reqProfile+ " -> "+getGLVersion());
                    }
                }
            }
        }
        GLContextShareSet.contextCreated(this);
        return CONTEXT_CURRENT_NEW;
      }
      makeCurrentImpl();
      return CONTEXT_CURRENT;
  }
  protected abstract void makeCurrentImpl() throws GLException;

  /**
   * Calls {@link GLDrawableImpl#associateContext(GLContext, boolean)}
   */
  protected void associateDrawable(boolean bound) {
      drawable.associateContext(this, bound);
  }

  /**
   * Calls {@link GLDrawableImpl#contextMadeCurrent(GLContext, boolean)}
   */
  protected void contextMadeCurrent(boolean current) {
      drawable.contextMadeCurrent(this, current);
  }

  /**
   * Platform dependent entry point for context creation.<br>
   *
   * This method is called from {@link #makeCurrentWithinLock()} .. {@link #makeCurrent()} .<br>
   *
   * The implementation shall verify this context with a
   * <code>MakeContextCurrent</code> call.<br>
   *
   * The implementation <b>must</b> leave the context current.<br>
   *
   * @param share the shared context or null
   * @return the valid and current context if successful, or null
   * @throws GLException
   */
  protected abstract boolean createImpl(long sharedWithHandle) throws GLException ;

  /**
   * Platform dependent but harmonized implementation of the <code>ARB_create_context</code>
   * mechanism to create a context.<br>
   *
   * This method is called from {@link #createContextARB}, {@link #createImpl(long)} .. {@link #makeCurrent()} .<br>
   *
   * The implementation shall verify this context with a
   * <code>MakeContextCurrent</code> call.<br>
   *
   * The implementation <b>must</b> leave the context current.<br>
   *
   * @param share the shared context or null
   * @param direct flag if direct is requested
   * @param ctxOptionFlags <code>ARB_create_context</code> related, see references below
   * @param major major number
   * @param minor minor number
   * @return the valid and current context if successful, or null
   *
   * @see #makeCurrent
   * @see #CTX_PROFILE_COMPAT
   * @see #CTX_OPTION_FORWARD
   * @see #CTX_OPTION_DEBUG
   * @see #makeCurrentImpl
   * @see #create
   * @see #createContextARB
   * @see #createContextARBImpl
   * @see #destroyContextARBImpl
   */
  protected abstract long createContextARBImpl(long share, boolean direct, int ctxOptionFlags, int major, int minor);

  /**
   * Destroy the context created by {@link #createContextARBImpl}.
   *
   * @see #makeCurrent
   * @see #makeCurrentImpl
   * @see #create
   * @see #createContextARB
   * @see #createContextARBImpl
   * @see #destroyContextARBImpl
   */
  protected abstract void destroyContextARBImpl(long context);

  /**
   * Platform independent part of using the <code>ARB_create_context</code>
   * mechanism to create a context.<br>
   *
   * The implementation of {@link #create} shall use this protocol in case the platform supports <code>ARB_create_context</code>.<br>
   *
   * This method may call {@link #createContextARBImpl} and {@link #destroyContextARBImpl}. <br>
   *
   * This method will also query all available native OpenGL context when first called,<br>
   * usually the first call should happen with the shared GLContext of the DrawableFactory.<br>
   *
   * The implementation makes the context current, if successful<br>
   *
   * @see #makeCurrentImpl
   * @see #create
   * @see #createContextARB
   * @see #createContextARBImpl
   * @see #destroyContextARBImpl
   */
  protected final long createContextARB(final long share, final boolean direct)
  {
    final AbstractGraphicsConfiguration config = drawable.getNativeSurface().getGraphicsConfiguration();
    final AbstractGraphicsDevice device = config.getScreen().getDevice();

    if (DEBUG) {
      System.err.println(getThreadName() + ": createContextARB: mappedVersionsAvailableSet("+device.getConnection()+"): "+
               GLContext.getAvailableGLVersionsSet(device));
    }

    if ( !GLContext.getAvailableGLVersionsSet(device) ) {
        if(!mapGLVersions(device)) {
            // none of the ARB context creation calls was successful, bail out
            return 0;
        }
    }

    final GLCapabilitiesImmutable glCaps = (GLCapabilitiesImmutable) config.getChosenCapabilities();
    final int[] reqMajorCTP = new int[] { 0, 0 };
    GLContext.getRequestMajorAndCompat(glCaps.getGLProfile(), reqMajorCTP);

    if(DEBUG) {
        System.err.println(getThreadName() + ": createContextARB: Requested "+GLContext.getGLVersion(reqMajorCTP[0], 0, reqMajorCTP[0], null));
    }
    int _major[] = { 0 };
    int _minor[] = { 0 };
    int _ctp[] = { 0 };
    long _ctx = 0;
    if( GLContext.getAvailableGLVersion(device, reqMajorCTP[0], reqMajorCTP[1],
                                        _major, _minor, _ctp)) {
        _ctp[0] |= additionalCtxCreationFlags;
        if(DEBUG) {
            System.err.println(getThreadName() + ": createContextARB: Mapped "+GLContext.getGLVersion(_major[0], _minor[0], _ctp[0], null));
        }
        _ctx = createContextARBImpl(share, direct, _ctp[0], _major[0], _minor[0]);
        if(0!=_ctx) {
            if( !setGLFunctionAvailability(true, _major[0], _minor[0], _ctp[0], false /* strictMatch */, false /* withinGLVersionsMapping */) ) {
                throw new InternalError("setGLFunctionAvailability !strictMatch failed");
            }
        }
    }
    return _ctx;
  }

  private final boolean mapGLVersions(AbstractGraphicsDevice device) {
    synchronized (GLContext.deviceVersionAvailable) {
        final long t0 = ( DEBUG ) ? System.nanoTime() : 0;
        boolean success = false;
        // Following GLProfile.GL_PROFILE_LIST_ALL order of profile detection { GL4bc, GL3bc, GL2, GL4, GL3, GL2GL3, GLES2, GL2ES2, GLES1, GL2ES1 }
        boolean hasGL4bc = false;
        boolean hasGL3bc = false;
        boolean hasGL2   = false;
        boolean hasGL4   = false;
        boolean hasGL3   = false;

        // Even w/ PROFILE_ALIASING, try to use true core GL profiles
        // ensuring proper user behavior across platforms due to different feature sets!
        //
        if( Platform.OSType.MACOS == Platform.getOSType() &&
            Platform.getOSVersionNumber().compareTo(Platform.OSXVersion.Mavericks) >= 0 ) {
            /**
             * OSX 10.9 GLRendererQuirks.GL4NeedsGL3Request, quirk is added as usual @ setRendererQuirks(..)
             */
            if( !hasGL4 && !hasGL3 ) {
                hasGL3   = createContextARBMapVersionsAvailable(3, CTX_PROFILE_CORE);    // GL3
                success |= hasGL3;
                if( hasGL3 ) {
                    final boolean isHWAccel = 0 == ( CTX_IMPL_ACCEL_SOFT & ctxOptions );
                    if( isHWAccel && ctxVersion.getMajor() >= 4 ) {
                        // Gotcha: Creating a '3.2' ctx delivers a >= 4 ctx.
                        GLContext.mapAvailableGLVersion(device, 4, CTX_PROFILE_CORE, ctxVersion.getMajor(), ctxVersion.getMinor(), ctxOptions);
                        hasGL4   = true;
                        if(DEBUG) {
                            System.err.println("Quirk Triggerd: "+GLRendererQuirks.toString(GLRendererQuirks.GL4NeedsGL3Request)+": cause: OS "+Platform.getOSType()+", OS Version "+Platform.getOSVersionNumber());
                        }
                    }
                    resetStates(false); // clean this context states, since creation was temporary
                }
            }
        }
        if( !hasGL4 ) {
            hasGL4   = createContextARBMapVersionsAvailable(4, CTX_PROFILE_CORE);    // GL4
            success |= hasGL4;
            if( hasGL4 ) {
                if( 0 == ( CTX_IMPL_ACCEL_SOFT & ctxOptions ) ) {
                    // Map hw-accel GL4 to all lower core profiles: GL3
                    GLContext.mapAvailableGLVersion(device, 3, CTX_PROFILE_CORE, ctxVersion.getMajor(), ctxVersion.getMinor(), ctxOptions);
                    if( PROFILE_ALIASING ) {
                        hasGL3   = true;
                    }
                }
                resetStates(false); // clean context states, since creation was temporary
            }
        }
        if( !hasGL3 ) {
            hasGL3   = createContextARBMapVersionsAvailable(3, CTX_PROFILE_CORE);    // GL3
            success |= hasGL3;
            if( hasGL3 ) {
                resetStates(false); // clean this context states, since creation was temporary
            }
        }
        if( !hasGL4bc ) {
            hasGL4bc = createContextARBMapVersionsAvailable(4, CTX_PROFILE_COMPAT);  // GL4bc
            success |= hasGL4bc;
            if( hasGL4bc ) {
                if( !hasGL4 ) { // last chance .. ignore hw-accel
                    GLContext.mapAvailableGLVersion(device, 4, CTX_PROFILE_CORE, ctxVersion.getMajor(), ctxVersion.getMinor(), ctxOptions);
                    hasGL4   = true;
                }
                if( !hasGL3 ) { // last chance .. ignore hw-accel
                    GLContext.mapAvailableGLVersion(device, 3, CTX_PROFILE_CORE, ctxVersion.getMajor(), ctxVersion.getMinor(), ctxOptions);
                    hasGL3   = true;
                }
                if( 0 == ( CTX_IMPL_ACCEL_SOFT & ctxOptions ) ) {
                    // Map hw-accel GL4bc to all lower compatible profiles: GL3bc, GL2
                    GLContext.mapAvailableGLVersion(device, 3, CTX_PROFILE_COMPAT, ctxVersion.getMajor(), ctxVersion.getMinor(), ctxOptions);
                    GLContext.mapAvailableGLVersion(device, 2, CTX_PROFILE_COMPAT, ctxVersion.getMajor(), ctxVersion.getMinor(), ctxOptions);
                    if(PROFILE_ALIASING) {
                        hasGL3bc = true;
                        hasGL2   = true;
                    }
                }
                resetStates(false); // clean this context states, since creation was temporary
            }
        }
        if( !hasGL3bc ) {
            hasGL3bc = createContextARBMapVersionsAvailable(3, CTX_PROFILE_COMPAT);  // GL3bc
            success |= hasGL3bc;
            if( hasGL3bc ) {
                if(!hasGL3) {  // last chance .. ignore hw-accel
                    GLContext.mapAvailableGLVersion(device, 3, CTX_PROFILE_CORE, ctxVersion.getMajor(), ctxVersion.getMinor(), ctxOptions);
                    hasGL3   = true;
                }
                if( 0 == ( CTX_IMPL_ACCEL_SOFT & ctxOptions ) ) {
                    // Map hw-accel GL3bc to all lower compatible profiles: GL2
                    GLContext.mapAvailableGLVersion(device, 2, CTX_PROFILE_COMPAT, ctxVersion.getMajor(), ctxVersion.getMinor(), ctxOptions);
                    if(PROFILE_ALIASING) {
                        hasGL2   = true;
                    }
                }
                resetStates(false); // clean this context states, since creation was temporary
            }
        }
        if( !hasGL2 ) {
            hasGL2   = createContextARBMapVersionsAvailable(2, CTX_PROFILE_COMPAT);  // GL2
            success |= hasGL2;
            if( hasGL2 ) {
                resetStates(false); // clean this context states, since creation was temporary
            }
        }
        if(success) {
            // only claim GL versions set [and hence detected] if ARB context creation was successful
            GLContext.setAvailableGLVersionsSet(device);
            if(DEBUG) {
                final long t1 = System.nanoTime();
                System.err.println("GLContextImpl.mapGLVersions: "+device+", profileAliasing: "+PROFILE_ALIASING+", total "+(t1-t0)/1e6 +"ms");
                System.err.println(GLContext.dumpAvailableGLVersions(null).toString());
            }
        } else if (DEBUG) {
            System.err.println(getThreadName() + ": createContextARB-MapVersions NONE for :"+device);
        }
        return success;
    }
  }

  /**
   * Note: Since context creation is temporary, caller need to issue {@link #resetStates(boolean)}, if creation was successful, i.e. returns true.
   * This method does not reset the states, allowing the caller to utilize the state variables.
   **/
  private final boolean createContextARBMapVersionsAvailable(int reqMajor, int reqProfile) {
    long _context;
    int ctp = CTX_IS_ARB_CREATED | reqProfile;

    // To ensure GL profile compatibility within the JOGL application
    // we always try to map against the highest GL version,
    // so the user can always cast to the highest available one.
    int majorMax, minorMax;
    int majorMin, minorMin;
    int major[] = new int[1];
    int minor[] = new int[1];
    if( 4 == reqMajor ) {
        majorMax=4; minorMax=GLContext.getMaxMinor(ctp, majorMax);
        majorMin=4; minorMin=0;
    } else if( 3 == reqMajor ) {
        majorMax=3; minorMax=GLContext.getMaxMinor(ctp, majorMax);
        majorMin=3; minorMin=1;
    } else /* if( glp.isGL2() ) */ {
        // our minimum desktop OpenGL runtime requirements are 1.1,
        // nevertheless we restrict ARB context creation to 2.0 to spare us futile attempts
        majorMax=3; minorMax=0;
        majorMin=2; minorMin=0;
    }
    _context = createContextARBVersions(0, true, ctp,
                                        /* max */ majorMax, minorMax,
                                        /* min */ majorMin, minorMin,
                                        /* res */ major, minor);

    if( 0 == _context && CTX_PROFILE_CORE == reqProfile && !PROFILE_ALIASING ) {
        // try w/ FORWARD instead of CORE
        ctp &= ~CTX_PROFILE_CORE ;
        ctp |=  CTX_OPTION_FORWARD ;
        _context = createContextARBVersions(0, true, ctp,
                                            /* max */ majorMax, minorMax,
                                            /* min */ majorMin, minorMin,
                                            /* res */ major, minor);
       if( 0 == _context ) {
            // Try a compatible one .. even though not requested .. last resort
            ctp &= ~CTX_PROFILE_CORE ;
            ctp &= ~CTX_OPTION_FORWARD ;
            ctp |=  CTX_PROFILE_COMPAT ;
            _context = createContextARBVersions(0, true, ctp,
                                       /* max */ majorMax, minorMax,
                                       /* min */ majorMin, minorMin,
                                       /* res */ major, minor);
       }
    }
    final boolean res;
    if( 0 != _context ) {
        AbstractGraphicsDevice device = drawable.getNativeSurface().getGraphicsConfiguration().getScreen().getDevice();
        // ctxMajorVersion, ctxMinorVersion, ctxOptions is being set by
        //   createContextARBVersions(..) -> setGLFunctionAvailbility(..) -> setContextVersion(..)
        GLContext.mapAvailableGLVersion(device, reqMajor, reqProfile, ctxVersion.getMajor(), ctxVersion.getMinor(), ctxOptions);
        destroyContextARBImpl(_context);
        if (DEBUG) {
          System.err.println(getThreadName() + ": createContextARB-MapVersionsAvailable HAVE: " +reqMajor+"."+reqProfile+ " -> "+getGLVersion());
        }
        res = true;
    } else {
        if (DEBUG) {
          System.err.println(getThreadName() + ": createContextARB-MapVersionsAvailable NOPE: "+reqMajor+"."+reqProfile);
        }
        res = false;
    }
    return res;
  }

  private final long createContextARBVersions(long share, boolean direct, int ctxOptionFlags,
                                              int majorMax, int minorMax,
                                              int majorMin, int minorMin,
                                              int major[], int minor[]) {
    major[0]=majorMax;
    minor[0]=minorMax;
    long _context=0;
    int i=0;

    do {
        if (DEBUG) {
            i++;
            System.err.println(getThreadName() + ": createContextARBVersions."+i+": share "+share+", direct "+direct+
                    ", version "+major[0]+"."+minor[0]+", major["+majorMin+".."+majorMax+"], minor["+minorMin+".."+minorMax+"]");
        }
        _context = createContextARBImpl(share, direct, ctxOptionFlags, major[0], minor[0]);

        if(0 != _context) {
            if( setGLFunctionAvailability(true, major[0], minor[0], ctxOptionFlags, true /* strictMatch */, true /* withinGLVersionsMapping */) ) {
                break;
            } else {
                destroyContextARBImpl(_context);
                _context = 0;
            }
        }

    } while ( ( major[0]>majorMin || major[0]==majorMin && minor[0] >minorMin ) &&  // #1 check whether version is above lower limit
              GLContext.decrementGLVersion(ctxOptionFlags, major, minor)            // #2 decrement version
            );
    if (DEBUG) {
        System.err.println(getThreadName() + ": createContextARBVersions.X: ctx "+toHexString(_context)+", share "+share+", direct "+direct+
                ", version "+major[0]+"."+minor[0]+", major["+majorMin+".."+majorMax+"], minor["+minorMin+".."+minorMax+"]");
    }
    return _context;
  }

  //----------------------------------------------------------------------
  // Managing the actual OpenGL version, usually figured at creation time.
  // As a last resort, the GL_VERSION string may be used ..
  //

  /**
   * If major > 0 || minor > 0 : Use passed values, determined at creation time
   * Otherwise .. don't touch ..
   */
  private final void setContextVersion(int major, int minor, int ctp, VersionNumberString glVendorVersion, boolean useGL) {
      if ( 0 == ctp ) {
        throw new GLException("Invalid GL Version "+major+"."+minor+", ctp "+toHexString(ctp));
      }
      ctxVersion = new VersionNumber(major, minor, 0);
      ctxVersionString = getGLVersion(major, minor, ctp, glVersion);
      ctxVendorVersion = glVendorVersion;
      ctxOptions = ctp;
      if(useGL) {
          ctxGLSLVersion = VersionNumber.zeroVersion;
          if( hasGLSL() ) { // >= ES2 || GL2.0
              final String glslVersion = isGLES() ? null : gl.glGetString(GL2ES2.GL_SHADING_LANGUAGE_VERSION) ; // Use static GLSL version for ES to be safe!
              if( null != glslVersion ) {
                  ctxGLSLVersion = new VersionNumber(glslVersion);
                  if( ctxGLSLVersion.getMajor() < 1 ) {
                      ctxGLSLVersion = VersionNumber.zeroVersion; // failed ..
                  }
              }
              if( ctxGLSLVersion.isZero() ) {
                  ctxGLSLVersion = getStaticGLSLVersionNumber(major, minor, ctxOptions);
              }
          }
      }
  }

  //----------------------------------------------------------------------
  // Helpers for various context implementations
  //

  private Object createInstance(GLProfile glp, String suffix, Class<?>[] cstrArgTypes, Object[] cstrArgs) {
    return ReflectionUtil.createInstance(glp.getGLImplBaseClassName()+suffix, cstrArgTypes, cstrArgs, getClass().getClassLoader());
  }

  private boolean verifyInstance(GLProfile glp, String suffix, Object instance) {
    return ReflectionUtil.instanceOf(instance, glp.getGLImplBaseClassName()+suffix);
  }

  /** Create the GL for this context. */
  protected GL createGL(GLProfile glp) {
    final GL gl = (GL) createInstance(glp, "Impl", new Class[] { GLProfile.class, GLContextImpl.class }, new Object[] { glp, this } );

    /* FIXME: refactor dependence on Java 2D / JOGL bridge
    if (tracker != null) {
      gl.setObjectTracker(tracker);
    }
    */
    return gl;
  }

  /**
   * Finalizes GL instance initialization after this context has been initialized.
   * <p>
   * Method calls 'void finalizeInit()' of instance 'gl' as retrieved by reflection, if exist.
   * </p>
   */
  private void finalizeInit(GL gl) {
      Method finalizeInit = null;
      try {
          finalizeInit = ReflectionUtil.getMethod(gl.getClass(), "finalizeInit", new Class<?>[]{ });
      } catch ( Throwable t ) {
          if(DEBUG) {
              System.err.println("Catched "+t.getClass().getName()+": "+t.getMessage());
              t.printStackTrace();
          }
      }
      if( null != finalizeInit ) {
          ReflectionUtil.callMethod(gl, finalizeInit, new Object[]{ });
      }
  }

  public final ProcAddressTable getGLProcAddressTable() {
    return glProcAddressTable;
  }

  /**
   * Shall return the platform extension ProcAddressTable,
   * ie for GLXExt, EGLExt, ..
   */
  public abstract ProcAddressTable getPlatformExtProcAddressTable();

  /**
   * Part of <code>GL_NV_vertex_array_range</code>.
   * <p>
   * Provides platform-independent access to the <code>wglAllocateMemoryNV</code> /
   * <code>glXAllocateMemoryNV</code>.
   * </p>
   */
  public abstract ByteBuffer glAllocateMemoryNV(int size, float readFrequency, float writeFrequency, float priority);

  /**
   * Part of <code>GL_NV_vertex_array_range</code>.
   * <p>
   * Provides platform-independent access to the <code>wglFreeMemoryNV</code> /
   * <code>glXFreeMemoryNV</code>.
   * </p>
   */
  public abstract void glFreeMemoryNV(ByteBuffer pointer);

  /** Maps the given "platform-independent" function name to a real function
      name. Currently this is only used to map "glAllocateMemoryNV" and
      associated routines to wglAllocateMemoryNV / glXAllocateMemoryNV. */
  protected final String mapToRealGLFunctionName(String glFunctionName) {
    Map<String, String> map = getFunctionNameMap();
    String lookup = ( null != map ) ? map.get(glFunctionName) : null;
    if (lookup != null) {
      return lookup;
    }
    return glFunctionName;
  }
  protected abstract Map<String, String> getFunctionNameMap() ;

  /** Maps the given "platform-independent" extension name to a real
      function name. Currently this is only used to map
      "GL_ARB_pbuffer"      to  "WGL_ARB_pbuffer/GLX_SGIX_pbuffer" and
      "GL_ARB_pixel_format" to  "WGL_ARB_pixel_format/n.a."
   */
  protected final String mapToRealGLExtensionName(String glExtensionName) {
    Map<String, String> map = getExtensionNameMap();
    String lookup = ( null != map ) ? map.get(glExtensionName) : null;
    if (lookup != null) {
      return lookup;
    }
    return glExtensionName;
  }
  protected abstract Map<String, String> getExtensionNameMap() ;

  /** Helper routine which resets a ProcAddressTable generated by the
      GLEmitter by looking up anew all of its function pointers. */
  protected final void resetProcAddressTable(final ProcAddressTable table) {
    AccessController.doPrivileged(new PrivilegedAction<Object>() {
        @Override
        public Object run() {
            table.reset(getDrawableImpl().getGLDynamicLookupHelper() );
            return null;
        }
    } );
  }

  private final boolean initGLRendererAndGLVersionStrings()  {
    final GLDynamicLookupHelper glDynLookupHelper = getDrawableImpl().getGLDynamicLookupHelper();
    final long _glGetString = glDynLookupHelper.dynamicLookupFunction("glGetString");
    if(0 == _glGetString) {
        System.err.println("Error: Entry point to 'glGetString' is NULL.");
        if(DEBUG) {
            Thread.dumpStack();
        }
        return false;
    } else {
        final String _glVendor = glGetStringInt(GL.GL_VENDOR, _glGetString);
        if(null == _glVendor) {
            if(DEBUG) {
                System.err.println("Warning: GL_VENDOR is NULL.");
                Thread.dumpStack();
            }
            return false;
        }
        glVendor = _glVendor;

        final String _glRenderer = glGetStringInt(GL.GL_RENDERER, _glGetString);
        if(null == _glRenderer) {
            if(DEBUG) {
                System.err.println("Warning: GL_RENDERER is NULL.");
                Thread.dumpStack();
            }
            return false;
        }
        glRenderer = _glRenderer;
        glRendererLowerCase = glRenderer.toLowerCase();

        final String _glVersion = glGetStringInt(GL.GL_VERSION, _glGetString);
        if(null == _glVersion) {
            // FIXME
            if(DEBUG) {
                System.err.println("Warning: GL_VERSION is NULL.");
                Thread.dumpStack();
            }
            return false;
        }
        glVersion = _glVersion;

        return true;
    }
  }

  /**
   * Returns null if version string is invalid, otherwise a valid instance.
   * <p>
   * Note: Non ARB ctx is limited to GL 3.0.
   * </p>
   */
  private static final VersionNumber getGLVersionNumber(int ctp, String glVersionStr) {
      if( null != glVersionStr ) {
          final GLVersionNumber version = GLVersionNumber.create(glVersionStr);
          if ( version.isValid() ) {
              int[] major = new int[] { version.getMajor() };
              int[] minor = new int[] { version.getMinor() };
              if ( GLContext.isValidGLVersion(ctp, major[0], minor[0]) ) {
                  return new VersionNumber(major[0], minor[0], 0);
              }
          }
      }
      return null;
  }

  /**
   * Returns false if <code>glGetIntegerv</code> is inaccessible, otherwise queries major.minor
   * version for given arrays.
   * <p>
   * If the GL query fails, major will be zero.
   * </p>
   */
  private final boolean getGLIntVersion(int[] glIntMajor, int[] glIntMinor)  {
    glIntMajor[0] = 0; // clear
    final GLDynamicLookupHelper glDynLookupHelper = getDrawableImpl().getGLDynamicLookupHelper();
    final long _glGetIntegerv = glDynLookupHelper.dynamicLookupFunction("glGetIntegerv");
    if( 0 == _glGetIntegerv ) {
        System.err.println("Error: Entry point to 'glGetIntegerv' is NULL.");
        if(DEBUG) {
            Thread.dumpStack();
        }
        return false;
    } else {
        glGetIntegervInt(GL2GL3.GL_MAJOR_VERSION, glIntMajor, 0, _glGetIntegerv);
        glGetIntegervInt(GL2GL3.GL_MINOR_VERSION, glIntMinor, 0, _glGetIntegerv);
        return true;
    }
  }

  protected final int getCtxOptions() {
      return ctxOptions;
  }


  /**
   * Sets the OpenGL implementation class and
   * the cache of which GL functions are available for calling through this
   * context. See {@link #isFunctionAvailable(String)} for more information on
   * the definition of "available".
   * <br>
   * All ProcaddressTables are being determined and cached, the GL version is being set
   * and the extension cache is determined as well.
   *
   * @param force force the setting, even if is already being set.
   *              This might be useful if you change the OpenGL implementation.
   * @param major OpenGL major version
   * @param minor OpenGL minor version
   * @param ctxProfileBits OpenGL context profile and option bits, see {@link javax.media.opengl.GLContext#CTX_OPTION_ANY}
   * @param strictMatch if <code>true</code> the ctx must
   *                    <ul>
   *                      <li>be greater or equal than the requested <code>major.minor</code> version, and</li>
   *                      <li>match the ctxProfileBits</li>
   *                      <li>match ES major versions</li>
   *                    </ul>, otherwise method aborts and returns <code>false</code>.<br>
   *                    if <code>false</code> no version check is performed.
   * @param withinGLVersionsMapping if <code>true</code> GL version mapping is in process, i.e. quering avail versions.
   *                                Otherwise normal user context creation.
   * @return returns <code>true</code> if successful, otherwise <code>false</code>.<br>
   *                 If <code>strictMatch</code> is <code>false</code> method shall always return <code>true</code> or throw an exception.
   *                 If <code>false</code> is returned, no data has been cached or mapped, i.e. ProcAddressTable, Extensions, Version, etc.
   * @see #setContextVersion
   * @see javax.media.opengl.GLContext#CTX_OPTION_ANY
   * @see javax.media.opengl.GLContext#CTX_PROFILE_COMPAT
   * @see javax.media.opengl.GLContext#CTX_IMPL_ES2_COMPAT
   */
  protected final boolean setGLFunctionAvailability(boolean force, int major, int minor, int ctxProfileBits,
                                                    boolean strictMatch, boolean withinGLVersionsMapping) {
    if(null!=this.gl && null!=glProcAddressTable && !force) {
        return true; // already done and not forced
    }

    if ( 0 < major && !GLContext.isValidGLVersion(ctxProfileBits, major, minor) ) {
        throw new GLException("Invalid GL Version Request "+GLContext.getGLVersion(major, minor, ctxProfileBits, null));
    }

    if(null==this.gl || !verifyInstance(gl.getGLProfile(), "Impl", this.gl)) {
        setGL( createGL( getGLDrawable().getGLProfile() ) );
    }
    updateGLXProcAddressTable();

    final AbstractGraphicsConfiguration aconfig = drawable.getNativeSurface().getGraphicsConfiguration();
    final AbstractGraphicsDevice adevice = aconfig.getScreen().getDevice();
    final int reqCtxProfileBits = ctxProfileBits;
    final VersionNumber reqGLVersion = new VersionNumber(major, minor, 0);
    final VersionNumber hasGLVersionByString;
    {
        final boolean initGLRendererAndGLVersionStringsOK = initGLRendererAndGLVersionStrings();
        if( !initGLRendererAndGLVersionStringsOK ) {
            final String errMsg = "Intialization of GL renderer strings failed. "+adevice+" - "+GLContext.getGLVersion(major, minor, ctxProfileBits, null);
            if( strictMatch ) {
                // query mode .. simply fail
                if(DEBUG) {
                    System.err.println("Warning: setGLFunctionAvailability: "+errMsg);
                }
                return false;
            } else {
                // unusable GL context - non query mode - hard fail!
                throw new GLException(errMsg);
            }
        } else {
            hasGLVersionByString = getGLVersionNumber(ctxProfileBits, glVersion);
            if(DEBUG) {
                System.err.println(getThreadName() + ": GLContext.setGLFuncAvail: Given "+adevice+
                                   " - "+GLContext.getGLVersion(major, minor, ctxProfileBits, glVersion)+
                                   ", Number(Str) "+hasGLVersionByString);
            }
        }
    }

    final boolean isES = 0 != ( CTX_PROFILE_ES & ctxProfileBits );

    //
    // Validate GL version either by GL-Integer or GL-String
    //
    if (DEBUG) {
        System.err.println(getThreadName() + ": GLContext.setGLFuncAvail: Pre version verification - expected "+GLContext.getGLVersion(major, minor, ctxProfileBits, null)+", strictMatch "+strictMatch+", glVersionsMapping " +withinGLVersionsMapping);
    }

    final boolean versionGL3IntOK;
    {
        // Validate the requested version w/ the GL-version from an integer query,
        // as supported by GL [ES] >= 3.0 implementation.
        final VersionNumber hasGLVersionByInt;
        {
            final int[] glIntMajor = new int[] { 0 }, glIntMinor = new int[] { 0 };
            final boolean getGLIntVersionOK = getGLIntVersion(glIntMajor, glIntMinor);
            if( !getGLIntVersionOK ) {
                final String errMsg = "Fetching GL Integer Version failed. "+adevice+" - "+GLContext.getGLVersion(major, minor, ctxProfileBits, null);
                if( strictMatch ) {
                    // query mode .. simply fail
                    if(DEBUG) {
                        System.err.println("Warning: setGLFunctionAvailability: "+errMsg);
                    }
                    return false;
                } else {
                    // unusable GL context - non query mode - hard fail!
                    throw new GLException(errMsg);
                }
            }
            hasGLVersionByInt = new VersionNumber(glIntMajor[0], glIntMinor[0], 0);
        }
        if (DEBUG) {
            System.err.println(getThreadName() + ": GLContext.setGLFuncAvail: Version verification (Int): String "+glVersion+", Number(Int) "+hasGLVersionByInt);
        }

        // Only validate integer based version if:
        //    - ctx >= 3.0 is requested _or_ string-version >= 3.0
        //    - _and_ a valid int version was fetched,
        // otherwise cont. w/ version-string method -> 3.0 > Version || Version > MAX!
        //
        if ( ( major >= 3 || hasGLVersionByString.compareTo(Version300) >= 0 ) &&
             GLContext.isValidGLVersion(ctxProfileBits, hasGLVersionByInt.getMajor(), hasGLVersionByInt.getMinor()) ) {
            // Strict Match (GLVersionMapping):
            //   Relaxed match for versions ( !isES && major < 3 ) requests, last resort!
            //   Otherwise:
            //     - fail if hasVersion < reqVersion (desktop and ES)
            //     - fail if ES major-version mismatch:
            //       - request 1, >= 3 must be equal
            //       - request 2 must be [2..3]
            //
            final int hasMajor = hasGLVersionByInt.getMajor();
            if( strictMatch &&
                ( ( ( isES || major >= 3 ) && hasGLVersionByInt.compareTo(reqGLVersion) < 0 ) ||
                  ( isES &&
                    (
                      ( 2 == major && ( 2 > hasMajor || hasMajor > 3 ) ) ||  // 2      -> [2..3]
                      ( ( 1 == major || 3 <= major ) && major != hasMajor )  // 1,3,.. -> equal
                    )
                  )
                ) ) {
                if(DEBUG) {
                    System.err.println(getThreadName() + ": GLContext.setGLFuncAvail.X: FAIL, GL version mismatch (Int): "+GLContext.getGLVersion(major, minor, ctxProfileBits, null)+" -> "+glVersion+", "+hasGLVersionByInt);
                }
                return false;
            }
            // Use returned GL version!
            major = hasGLVersionByInt.getMajor();
            minor = hasGLVersionByInt.getMinor();
            versionGL3IntOK = true;
        } else {
            versionGL3IntOK = false;
        }
    }
    final boolean versionValidated;

    if( versionGL3IntOK ) {
        versionValidated = true;
    } else {
        // Validate the requested version w/ the GL-version from the version string.
        if (DEBUG) {
            System.err.println(getThreadName() + ": GLContext.setGLFuncAvail: Version verification (String): String "+glVersion+", Number(Str) "+hasGLVersionByString);
        }

        // Only validate if a valid string version was fetched -> MIN > Version || Version > MAX!
        if( null != hasGLVersionByString ) {
            // Strict Match (GLVersionMapping):
            //   Relaxed match for versions ( !isES && major < 3 ) requests, last resort!
            //   Otherwise:
            //     - fail if hasVersion < reqVersion (desktop and ES)
            //     - fail if ES major-version mismatch:
            //       - request 1, >= 3 must be equal
            //       - request 2 must be [2..3]
            //
            final int hasMajor = hasGLVersionByString.getMajor();
            if( strictMatch &&
                ( ( ( isES || major >= 3 ) && hasGLVersionByString.compareTo(reqGLVersion) < 0 ) ||
                  ( isES &&
                    (
                      ( 2 == major && ( 2 > hasMajor || hasMajor > 3 ) ) ||  // 2      -> [2..3]
                      ( ( 1 == major || 3 <= major ) && major != hasMajor )  // 1,3,.. -> equal
                    )
                  )
                ) ) {
                if(DEBUG) {
                    System.err.println(getThreadName() + ": GLContext.setGLFuncAvail.X: FAIL, GL version mismatch (String): "+GLContext.getGLVersion(major, minor, ctxProfileBits, null)+" -> "+glVersion+", "+hasGLVersionByString);
                }
                return false;
            }
            if( strictMatch && !versionGL3IntOK && major >= 3 ) {
                if(DEBUG) {
                    System.err.println(getThreadName() + ": GLContext.setGLFuncAvail.X: FAIL, GL3/ES3 version Int failed, String: "+GLContext.getGLVersion(major, minor, ctxProfileBits, null)+" -> "+glVersion+", "+hasGLVersionByString);
                }
                return false;
            }
            // Use returned GL version!
            major = hasGLVersionByString.getMajor();
            minor = hasGLVersionByString.getMinor();
            versionValidated = true;
        } else {
            versionValidated = false;
        }
    }
    if( strictMatch && !versionValidated ) {
        if(DEBUG) {
            System.err.println(getThreadName() + ": GLContext.setGLFuncAvail.X: FAIL, No GL version validation possible: "+GLContext.getGLVersion(major, minor, ctxProfileBits, null)+" -> "+glVersion);
        }
        return false;
    }
    if (DEBUG) {
        System.err.println(getThreadName() + ": GLContext.setGLFuncAvail: Post version verification req "+
                GLContext.getGLVersion(reqGLVersion.getMajor(), reqGLVersion.getMinor(), reqCtxProfileBits, null)+" -> has "+
                GLContext.getGLVersion(major, minor, ctxProfileBits, null)+
                ", strictMatch "+strictMatch+", versionValidated "+versionValidated+", versionGL3IntOK "+versionGL3IntOK);
    }

    if( major < 2 ) { // there is no ES2/3-compat for a profile w/ major < 2
        ctxProfileBits &= ~ ( GLContext.CTX_IMPL_ES2_COMPAT | GLContext.CTX_IMPL_ES3_COMPAT ) ;
    }

    final VersionNumberString vendorVersion = GLVersionNumber.createVendorVersion(glVersion);

    setRendererQuirks(adevice, reqGLVersion.getMajor(), reqGLVersion.getMinor(), reqCtxProfileBits, major, minor, ctxProfileBits, vendorVersion, withinGLVersionsMapping);

    if( strictMatch && glRendererQuirks.exist(GLRendererQuirks.GLNonCompliant) ) {
        if(DEBUG) {
            System.err.println(getThreadName() + ": GLContext.setGLFuncAvail.X: FAIL, GL is not compliant: "+GLContext.getGLVersion(major, minor, ctxProfileBits, glVersion)+", "+glRenderer);
        }
        return false;
    }

    if(!isCurrentContextHardwareRasterizer()) {
        ctxProfileBits |= GLContext.CTX_IMPL_ACCEL_SOFT;
    }

    contextFQN = getContextFQN(adevice, major, minor, ctxProfileBits);
    if (DEBUG) {
        System.err.println(getThreadName() + ": GLContext.setGLFuncAvail.0 validated FQN: "+contextFQN+" - "+GLContext.getGLVersion(major, minor, ctxProfileBits, glVersion));
    }

    //
    // UpdateGLProcAddressTable functionality
    //
    ProcAddressTable table = null;
    synchronized(mappedContextTypeObjectLock) {
        table = mappedGLProcAddress.get( contextFQN );
        if(null != table && !verifyInstance(gl.getGLProfile(), "ProcAddressTable", table)) {
            throw new InternalError("GLContext GL ProcAddressTable mapped key("+contextFQN+" - " + GLContext.getGLVersion(major, minor, ctxProfileBits, null)+
                  ") -> "+ table.getClass().getName()+" not matching "+gl.getGLProfile().getGLImplBaseClassName());
        }
    }
    if(null != table) {
        glProcAddressTable = table;
        if(DEBUG) {
            System.err.println(getThreadName() + ": GLContext GL ProcAddressTable reusing key("+contextFQN+") -> "+toHexString(table.hashCode()));
        }
    } else {
        glProcAddressTable = (ProcAddressTable) createInstance(gl.getGLProfile(), "ProcAddressTable",
                                                               new Class[] { FunctionAddressResolver.class } ,
                                                               new Object[] { new GLProcAddressResolver() } );
        resetProcAddressTable(getGLProcAddressTable());
        synchronized(mappedContextTypeObjectLock) {
            mappedGLProcAddress.put(contextFQN, getGLProcAddressTable());
            if(DEBUG) {
                System.err.println(getThreadName() + ": GLContext GL ProcAddressTable mapping key("+contextFQN+") -> "+toHexString(getGLProcAddressTable().hashCode()));
            }
        }
    }

    //
    // Update ExtensionAvailabilityCache
    //
    ExtensionAvailabilityCache eCache;
    synchronized(mappedContextTypeObjectLock) {
        eCache = mappedExtensionAvailabilityCache.get( contextFQN );
    }
    if(null !=  eCache) {
        extensionAvailability = eCache;
        if(DEBUG) {
            System.err.println(getThreadName() + ": GLContext GL ExtensionAvailabilityCache reusing key("+contextFQN+") -> "+toHexString(eCache.hashCode()) + " - entries: "+eCache.getTotalExtensionCount());
        }
    } else {
        extensionAvailability = new ExtensionAvailabilityCache();
        setContextVersion(major, minor, ctxProfileBits, vendorVersion, false); // pre-set of GL version, required for extension cache usage
        extensionAvailability.reset(this);
        synchronized(mappedContextTypeObjectLock) {
            mappedExtensionAvailabilityCache.put(contextFQN, extensionAvailability);
            if(DEBUG) {
                System.err.println(getThreadName() + ": GLContext GL ExtensionAvailabilityCache mapping key("+contextFQN+") -> "+toHexString(extensionAvailability.hashCode()) + " - entries: "+extensionAvailability.getTotalExtensionCount());
            }
        }
    }

    if( isES ) {
        if( major >= 3 ) {
            ctxProfileBits |= CTX_IMPL_ES3_COMPAT | CTX_IMPL_ES2_COMPAT ;
            ctxProfileBits |= CTX_IMPL_FBO;
        } else if( major >= 2 ) {
            ctxProfileBits |= CTX_IMPL_ES2_COMPAT;
            ctxProfileBits |= CTX_IMPL_FBO;
        }
    } else if( ( major > 4 || major == 4 && minor >= 3 ) ||
               ( ( major > 3 || major == 3 && minor >= 1 ) && isExtensionAvailable( GLExtensions.ARB_ES3_compatibility ) ) ) {
        // See GLContext.isGLES3CompatibleAvailable(..)/isGLES3Compatible()
        //   Includes [ GL &ge; 4.3, GL &ge; 3.1 w/ GL_ARB_ES3_compatibility and GLES3 ]
        ctxProfileBits |= CTX_IMPL_ES3_COMPAT | CTX_IMPL_ES2_COMPAT ;
        ctxProfileBits |= CTX_IMPL_FBO;
    } else if( isExtensionAvailable( GLExtensions.ARB_ES2_compatibility ) ) {
        ctxProfileBits |= CTX_IMPL_ES2_COMPAT;
        ctxProfileBits |= CTX_IMPL_FBO;
    } else if( hasFBOImpl(major, ctxProfileBits, extensionAvailability) ) {
        ctxProfileBits |= CTX_IMPL_FBO;
    }

    if( ( isES && major == 1 ) ||  isExtensionAvailable(GLExtensions.OES_single_precision) ) {
        ctxProfileBits |= CTX_IMPL_FP32_COMPAT_API;
    }

    if(FORCE_NO_FBO_SUPPORT) {
        ctxProfileBits &= ~CTX_IMPL_FBO ;
    }

    //
    // Set GL Version (complete w/ version string)
    //
    setContextVersion(major, minor, ctxProfileBits, vendorVersion, true);

    finalizeInit(gl);

    setDefaultSwapInterval();

    final int glErrX = gl.glGetError(); // clear GL error, maybe caused by above operations

    if(DEBUG) {
        System.err.println(getThreadName() + ": GLContext.setGLFuncAvail.X: OK "+contextFQN+" - "+GLContext.getGLVersion(ctxVersion.getMajor(), ctxVersion.getMinor(), ctxOptions, null)+" - glErr "+toHexString(glErrX));
    }
    return true;
  }

  private final void setRendererQuirks(final AbstractGraphicsDevice adevice,
                                       int reqMajor, int reqMinor, int reqCTP,
                                       int major, int minor, int ctp, final VersionNumberString vendorVersion,
                                       boolean withinGLVersionsMapping) {
    int[] quirks = new int[GLRendererQuirks.COUNT + 1]; // + 1 ( NoFullFBOSupport )
    int i = 0;

    final String MesaSP = "Mesa ";
    // final String MesaRendererAMDsp = " AMD ";
    final String MesaRendererIntelsp = "Intel(R)";
    final boolean hwAccel = 0 == ( ctp & GLContext.CTX_IMPL_ACCEL_SOFT );
    final boolean compatCtx = 0 != ( ctp & GLContext.CTX_PROFILE_COMPAT );
    final boolean esCtx = 0 != ( ctp & GLContext.CTX_PROFILE_ES );
    final boolean isX11 = NativeWindowFactory.TYPE_X11 == NativeWindowFactory.getNativeWindowType(true);
    final boolean isWindows = Platform.getOSType() == Platform.OSType.WINDOWS;
    final boolean isDriverMesa = glRenderer.contains(MesaSP) || glRenderer.contains("Gallium ");
    final boolean isDriverATICatalyst = !isDriverMesa && ( glVendor.contains("ATI Technologies") || glRenderer.startsWith("ATI ") );
    final boolean isDriverNVIDIAGeForce = !isDriverMesa && ( glVendor.contains("NVIDIA Corporation") || glRenderer.contains("NVIDIA ") );

    //
    // General Quirks
    //
    if( esCtx ) {
        final int quirk = GLRendererQuirks.GLES3ViaEGLES2Config;
        if( GLRendererQuirks.existStickyDeviceQuirk( GLDrawableFactory.getEGLFactory().getDefaultDevice(), GLRendererQuirks.GLES3ViaEGLES2Config) ) {
            // Merge default sticky quirk!
            if(DEBUG) {
                System.err.println("Quirk: "+GLRendererQuirks.toString(quirk)+": cause: Default EGL Device");
            }
            quirks[i++] = quirk;
        } else if( 2 == reqMajor && 2 < major ) {
            if(DEBUG) {
                System.err.println("Quirk: "+GLRendererQuirks.toString(quirk)+": cause: ES req "+reqMajor+" and 2 < "+major);
            }
            quirks[i++] = quirk;
            if( withinGLVersionsMapping ) {
                // Thread safe due to single threaded initialization!
                GLRendererQuirks.addStickyDeviceQuirks(adevice, quirks, i-1, 1);
            } else {
                // FIXME: Remove when moving EGL/ES to ARB ctx creation
                synchronized(GLContextImpl.class) {
                    GLRendererQuirks.addStickyDeviceQuirks(adevice, quirks, i-1, 1);
                }
            }
        }
    }

    //
    // OS related quirks
    //
    if( Platform.getOSType() == Platform.OSType.MACOS ) {
        //
        // OSX
        //
        {
            final int quirk = GLRendererQuirks.NoOffscreenBitmap;
            if(DEBUG) {
                System.err.println("Quirk: "+GLRendererQuirks.toString(quirk)+": cause: OS "+Platform.getOSType());
            }
            quirks[i++] = quirk;
        }
        if( Platform.getOSVersionNumber().compareTo(Platform.OSXVersion.Mavericks) >= 0 && 3==reqMajor && 4==major ) {
            final int quirk = GLRendererQuirks.GL4NeedsGL3Request;
            if(DEBUG) {
                System.err.println("Quirk: "+GLRendererQuirks.toString(quirk)+": cause: OS "+Platform.getOSType()+", OS Version "+Platform.getOSVersionNumber()+", req "+reqMajor+"."+reqMinor);
            }
            quirks[i++] = quirk;
            if( withinGLVersionsMapping ) {
                // Thread safe due to single threaded initialization!
                GLRendererQuirks.addStickyDeviceQuirks(adevice, quirks, i-1, 1);
            }
        }
        if( isDriverNVIDIAGeForce ) {
            final VersionNumber osxVersionNVFlushClean = new VersionNumber(10,7,3); // < OSX 10.7.3 w/ NV needs glFlush
            if( Platform.getOSVersionNumber().compareTo(osxVersionNVFlushClean) < 0 ) {
                final int quirk = GLRendererQuirks.GLFlushBeforeRelease;
                if(DEBUG) {
                    System.err.println("Quirk: "+GLRendererQuirks.toString(quirk)+": cause: OS "+Platform.getOSType()+", OS Version "+Platform.getOSVersionNumber()+", Renderer "+glRenderer);
                }
                quirks[i++] = quirk;
            }
            if( Platform.getOSVersionNumber().compareTo(Platform.OSXVersion.Lion) < 0 ) { // < OSX 10.7.0 w/ NV has unstable GLSL
                final int quirk = GLRendererQuirks.GLSLNonCompliant;
                if(DEBUG) {
                    System.err.println("Quirk: "+GLRendererQuirks.toString(quirk)+": cause: OS "+Platform.getOSType()+", OS Version "+Platform.getOSVersionNumber()+", Renderer "+glRenderer);
                }
                quirks[i++] = quirk;
            }
        }
    } else if( isWindows ) {
        //
        // WINDOWS
        //
        {
            final int quirk = GLRendererQuirks.NoDoubleBufferedBitmap;
            if(DEBUG) {
                System.err.println("Quirk: "+GLRendererQuirks.toString(quirk)+": cause: OS "+Platform.getOSType());
            }
            quirks[i++] = quirk;
        }

        if( isDriverATICatalyst ) {
            final VersionNumber winXPVersionNumber = new VersionNumber ( 5, 1, 0);
            final VersionNumber amdSafeMobilityVersion = new VersionNumber(12, 102, 3);

            if ( vendorVersion.compareTo(amdSafeMobilityVersion) < 0 ) { // includes: vendorVersion.isZero()
                final int quirk = GLRendererQuirks.NeedCurrCtx4ARBCreateContext;
                if(DEBUG) {
                    System.err.println("Quirk: "+GLRendererQuirks.toString(quirk)+": cause: OS "+Platform.getOSType()+", [Vendor "+glVendor+" or Renderer "+glRenderer+"], driverVersion "+vendorVersion);
                }
                quirks[i++] = quirk;
            }

            if( Platform.getOSVersionNumber().compareTo(winXPVersionNumber) <= 0 ) {
                final int quirk = GLRendererQuirks.NeedCurrCtx4ARBPixFmtQueries;
                if(DEBUG) {
                    System.err.println("Quirk: "+GLRendererQuirks.toString(quirk)+": cause: OS-Version "+Platform.getOSType()+" "+Platform.getOSVersionNumber()+", [Vendor "+glVendor+" or Renderer "+glRenderer+"]");
                }
                quirks[i++] = quirk;
            }
        }
    } else if( Platform.OSType.ANDROID == Platform.getOSType() ) {
        //
        // ANDROID
        //
        // Renderer related quirks, may also involve OS
        if( glRenderer.contains("PowerVR") ) {
            final int quirk = GLRendererQuirks.NoSetSwapInterval;
            if(DEBUG) {
                System.err.println("Quirk: "+GLRendererQuirks.toString(quirk)+": cause: OS "+Platform.getOSType() + " / Renderer " + glRenderer);
            }
            quirks[i++] = quirk;
        }
        if( glRenderer.contains("Immersion.16") ) {
          final int quirk = GLRendererQuirks.GLSharedContextBuggy;
          if(DEBUG) {
              System.err.println("Quirk: "+GLRendererQuirks.toString(quirk)+": cause: OS "+Platform.getOSType() + " / Renderer " + glRenderer);
          }
          quirks[i++] = quirk;
        }
    }

    //
    // Windowing Toolkit related quirks
    //
    if( isX11 ) {
        //
        // X11
        //
        {
            //
            // Quirk: DontCloseX11Display
            //
            final int quirk = GLRendererQuirks.DontCloseX11Display;
            if( glRenderer.contains(MesaSP) ) {
                if ( glRenderer.contains("X11") && vendorVersion.compareTo(Version800) < 0 ) {
                    if(DEBUG) {
                        System.err.println("Quirk: "+GLRendererQuirks.toString(quirk)+": cause: X11 Renderer=" + glRenderer + ", Version=[vendor " + vendorVersion + ", GL " + glVersion+"]");
                    }
                    quirks[i++] = quirk;
                }
            } else if( isDriverATICatalyst ) {
                {
                    if(DEBUG) {
                        System.err.println("Quirk: "+GLRendererQuirks.toString(quirk)+": cause: X11 Renderer=" + glRenderer);
                    }
                    quirks[i++] = quirk;
                }
            } else if( jogamp.nativewindow.x11.X11Util.getMarkAllDisplaysUnclosable() ) {
                {
                    if(DEBUG) {
                        System.err.println("Quirk: "+GLRendererQuirks.toString(quirk)+": cause: X11Util Downstream");
                    }
                    quirks[i++] = quirk;
                }
            }
        }
    }


    //
    // RENDERER related quirks
    //
    if( isDriverMesa ) {
        final VersionNumber mesaIntelBuggySharedCtx921 = new VersionNumber(9, 2, 1);

        {
            final int quirk = GLRendererQuirks.NoSetSwapIntervalPostRetarget;
            if(DEBUG) {
                System.err.println("Quirk: "+GLRendererQuirks.toString(quirk)+": cause: Renderer " + glRenderer);
            }
            quirks[i++] = quirk;
        }
        if( hwAccel /* glRenderer.contains( MesaRendererIntelsp ) || glRenderer.contains( MesaRendererAMDsp ) */ )
        {
            final int quirk = GLRendererQuirks.NoDoubleBufferedPBuffer;
            if(DEBUG) {
                System.err.println("Quirk: "+GLRendererQuirks.toString(quirk)+": cause: Renderer " + glRenderer);
            }
            quirks[i++] = quirk;
        }
        if (compatCtx && (major > 3 || (major == 3 && minor >= 1))) {
          // FIXME: Apply vendor version constraints!
          final int quirk = GLRendererQuirks.GLNonCompliant;
          if(DEBUG) {
              System.err.println("Quirk: "+GLRendererQuirks.toString(quirk)+": cause: Renderer " + glRenderer);
          }
          quirks[i++] = quirk;
        }
        if( glRenderer.contains( MesaRendererIntelsp ) &&
            vendorVersion.compareTo(mesaIntelBuggySharedCtx921) >= 0 && isX11 ) {
          final int quirk = GLRendererQuirks.GLSharedContextBuggy;
          if(DEBUG) {
              System.err.println("Quirk: "+GLRendererQuirks.toString(quirk)+": cause: X11 / Renderer " + glRenderer + " / Mesa-Version "+vendorVersion);
          }
          quirks[i++] = quirk;
        }
        if( isWindows && glRenderer.contains("SVGA3D") ) {
            final VersionNumber mesaSafeFBOVersion = new VersionNumber(8, 0, 0);
            if ( vendorVersion.compareTo(mesaSafeFBOVersion) < 0 ) { // includes: vendorVersion.isZero()
                final int quirk = GLRendererQuirks.NoFullFBOSupport;
                if(DEBUG) {
                    System.err.println("Quirk: "+GLRendererQuirks.toString(quirk)+": cause: OS "+Platform.getOSType() + " / Renderer " + glRenderer + " / Mesa-Version "+vendorVersion);
                }
                quirks[i++] = quirk;
            }
        }
    }

    //
    // Property related quirks
    //
    if( FORCE_MIN_FBO_SUPPORT ) {
        final int quirk = GLRendererQuirks.NoFullFBOSupport;
        if(DEBUG) {
            System.err.println("Quirk: "+GLRendererQuirks.toString(quirk)+": cause: property");
        }
        quirks[i++] = quirk;
    }

    glRendererQuirks = new GLRendererQuirks(quirks, 0, i);
    GLRendererQuirks.pushStickyDeviceQuirks(adevice, glRendererQuirks); // Thread safe due to single threaded initialization!
    if(DEBUG) {
        System.err.println("Quirks local: "+glRendererQuirks);
        System.err.println("Quirks sticky on "+adevice+": "+GLRendererQuirks.getStickyDeviceQuirks(adevice));
    }
  }

  private static final boolean hasFBOImpl(int major, int ctp, ExtensionAvailabilityCache extCache) {
    return ( 0 != (ctp & CTX_PROFILE_ES) && major >= 2 ) ||   // ES >= 2.0

           major >= 3 ||                                                 // any >= 3.0 GL ctx

           ( null != extCache &&

               extCache.isExtensionAvailable(GLExtensions.ARB_ES2_compatibility)  ||         // ES 2.0 compatible

               extCache.isExtensionAvailable(GLExtensions.ARB_framebuffer_object) ||         // ARB_framebuffer_object

               extCache.isExtensionAvailable(GLExtensions.EXT_framebuffer_object) ||         // EXT_framebuffer_object

               extCache.isExtensionAvailable(GLExtensions.OES_framebuffer_object) ) ;        // OES_framebuffer_object excluded
  }

  private final void removeCachedVersion(int major, int minor, int ctxProfileBits) {
    if(!isCurrentContextHardwareRasterizer()) {
        ctxProfileBits |= GLContext.CTX_IMPL_ACCEL_SOFT;
    }
    final AbstractGraphicsConfiguration aconfig = drawable.getNativeSurface().getGraphicsConfiguration();
    final AbstractGraphicsDevice adevice = aconfig.getScreen().getDevice();

    contextFQN = getContextFQN(adevice, major, minor, ctxProfileBits);
    if (DEBUG) {
      System.err.println(getThreadName() + ": RM Context FQN: "+contextFQN+" - "+GLContext.getGLVersion(major, minor, ctxProfileBits, null));
    }

    synchronized(mappedContextTypeObjectLock) {
        final ProcAddressTable table = mappedGLProcAddress.remove( contextFQN );
        if(DEBUG) {
            final int hc = null != table ? table.hashCode() : 0;
            System.err.println(getThreadName() + ": RM GLContext GL ProcAddressTable mapping key("+contextFQN+") -> "+toHexString(hc));
        }
    }

    synchronized(mappedContextTypeObjectLock) {
        final ExtensionAvailabilityCache  eCache = mappedExtensionAvailabilityCache.remove( contextFQN );
        if(DEBUG) {
            final int hc = null != eCache ? eCache.hashCode() : 0;
            System.err.println(getThreadName() + ": RM GLContext GL ExtensionAvailabilityCache mapping key("+contextFQN+") -> "+toHexString(hc));
        }
    }
  }

  private final boolean isCurrentContextHardwareRasterizer()  {
    boolean isHardwareRasterizer = true;

    if(!drawable.getChosenGLCapabilities().getHardwareAccelerated()) {
        isHardwareRasterizer = false;
    } else {
        isHardwareRasterizer = ! ( glRendererLowerCase.contains("software") /* Mesa3D, Apple */ ||
                                   glRendererLowerCase.contains("mesa x11") /* Mesa3D  */ ||
                                   glRendererLowerCase.contains("softpipe") /* Gallium */ ||
                                   glRendererLowerCase.contains("llvmpipe") /* Gallium */
                                 );
    }
    return isHardwareRasterizer;
  }

  /**
   * Updates the platform's 'GLX' function cache
   */
  protected abstract void updateGLXProcAddressTable();

  protected abstract StringBuilder getPlatformExtensionsStringImpl();

  @Override
  public final boolean isFunctionAvailable(String glFunctionName) {
    // Check GL 1st (cached)
    if(null!=glProcAddressTable) { // null if this context wasn't not created
        try {
            if( glProcAddressTable.isFunctionAvailable( glFunctionName ) ) {
                return true;
            }
        } catch (Exception e) {}
    }

    // Check platform extensions 2nd (cached) - context had to be enabled once
    final ProcAddressTable pTable = getPlatformExtProcAddressTable();
    if(null!=pTable) {
        try {
            if( pTable.isFunctionAvailable( glFunctionName ) ) {
                return true;
            }
        } catch (Exception e) {}
    }

    // dynamic function lookup at last incl name aliasing (not cached)
    final DynamicLookupHelper dynLookup = getDrawableImpl().getGLDynamicLookupHelper();
    final String tmpBase = GLNameResolver.normalizeVEN(GLNameResolver.normalizeARB(glFunctionName, true), true);
    boolean res = false;
    int  variants = GLNameResolver.getFuncNamePermutationNumber(tmpBase);
    for(int i = 0; !res && i < variants; i++) {
        final String tmp = GLNameResolver.getFuncNamePermutation(tmpBase, i);
        try {
            res = dynLookup.isFunctionAvailable(tmp);
        } catch (Exception e) { }
    }
    return res;
  }

  @Override
  public final boolean isExtensionAvailable(String glExtensionName) {
      if(null!=extensionAvailability) {
        return extensionAvailability.isExtensionAvailable(mapToRealGLExtensionName(glExtensionName));
      }
      return false;
  }

  @Override
  public final int getPlatformExtensionCount() {
      return null != extensionAvailability ? extensionAvailability.getPlatformExtensionCount() : 0;
  }

  @Override
  public final String getPlatformExtensionsString() {
      if(null!=extensionAvailability) {
        return extensionAvailability.getPlatformExtensionsString();
      }
      return null;
  }

  @Override
  public final int getGLExtensionCount() {
      return null != extensionAvailability ? extensionAvailability.getGLExtensionCount() : 0;
  }

  @Override
  public final String getGLExtensionsString() {
      if(null!=extensionAvailability) {
        return extensionAvailability.getGLExtensionsString();
      }
      return null;
  }

  public final boolean isExtensionCacheInitialized() {
      if(null!=extensionAvailability) {
        return extensionAvailability.isInitialized();
      }
      return false;
  }

  protected static String getContextFQN(AbstractGraphicsDevice device, int major, int minor, int ctxProfileBits) {
      // remove non-key values
      ctxProfileBits &= CTX_IMPL_CACHE_MASK;

      return device.getUniqueID() + "-" + toHexString(composeBits(major, minor, ctxProfileBits));
  }

  protected final String getContextFQN() {
      return contextFQN;
  }

  /** Indicates which floating-point pbuffer implementation is in
      use. Returns one of GLPbuffer.APPLE_FLOAT, GLPbuffer.ATI_FLOAT,
      or GLPbuffer.NV_FLOAT. */
  public int getFloatingPointMode() throws GLException {
    throw new GLException("Not supported on non-pbuffer contexts");
  }

  @Override
  public int getDefaultPixelDataType() {
      evalPixelDataType();
      return pixelDataType;
  }

  @Override
  public int getDefaultPixelDataFormat() {
      evalPixelDataType();
      return pixelDataFormat;
  }

  private final void evalPixelDataType() {
    if(!pixelDataEvaluated) {
        synchronized(this) {
            if(!pixelDataEvaluated) {
                boolean ok = false;
                /* if(isGL2GL3() && 3 == components) {
                    pixelDataInternalFormat=GL.GL_RGB;
                    pixelDataFormat=GL.GL_RGB;
                    pixelDataType = GL.GL_UNSIGNED_BYTE;
                    ok = true;
                } else */ if( isGLES2Compatible() || isExtensionAvailable(GLExtensions.OES_read_format) ) {
                    final int[] glImplColorReadVals = new int[] { 0, 0 };
                    gl.glGetIntegerv(GL.GL_IMPLEMENTATION_COLOR_READ_FORMAT, glImplColorReadVals, 0);
                    gl.glGetIntegerv(GL.GL_IMPLEMENTATION_COLOR_READ_TYPE, glImplColorReadVals, 1);
                    // pixelDataInternalFormat = (4 == components) ? GL.GL_RGBA : GL.GL_RGB;
                    pixelDataFormat = glImplColorReadVals[0];
                    pixelDataType = glImplColorReadVals[1];
                    ok = 0 != pixelDataFormat && 0 != pixelDataType;
                }
                if( !ok ) {
                    // RGBA read is safe for all GL profiles
                    // pixelDataInternalFormat = (4 == components) ? GL.GL_RGBA : GL.GL_RGB;
                    pixelDataFormat=GL.GL_RGBA;
                    pixelDataType = GL.GL_UNSIGNED_BYTE;
                }
                // TODO: Consider:
                // return gl.isGL2GL3()?GL2GL3.GL_UNSIGNED_INT_8_8_8_8_REV:GL.GL_UNSIGNED_SHORT_5_5_5_1;
                pixelDataEvaluated = true;
            }
        }
    }
  }

  //----------------------------------------------------------------------
  // Helpers for buffer object optimizations

  public final void setBufferSizeTracker(GLBufferSizeTracker bufferSizeTracker) {
    this.bufferSizeTracker = bufferSizeTracker;
  }

  public final GLBufferSizeTracker getBufferSizeTracker() {
    return bufferSizeTracker;
  }

  public final GLBufferStateTracker getBufferStateTracker() {
    return bufferStateTracker;
  }

  public final GLStateTracker getGLStateTracker() {
    return glStateTracker;
  }

  //---------------------------------------------------------------------------
  // Helpers for context optimization where the last context is left
  // current on the OpenGL worker thread
  //

  /**
   * Returns true if the given thread is owner, otherwise false.
   * <p>
   * Method exists merely for code validation of {@link #isCurrent()}.
   * </p>
   */
  public final boolean isOwner(Thread thread) {
      return lock.isOwner(thread);
  }

  /**
   * Returns true if there are other threads waiting for this GLContext to {@link #makeCurrent()}, otherwise false.
   * <p>
   * Since method does not perform any synchronization, accurate result are returned if lock is hold - only.
   * </p>
   */
  public final boolean hasWaiters() {
    return lock.getQueueLength()>0;
  }

  /**
   * Returns the number of hold locks. See {@link RecursiveLock#getHoldCount()} for semantics.
   * <p>
   * Since method does not perform any synchronization, accurate result are returned if lock is hold - only.
   * </p>
   */
  public final int getLockCount() {
      return lock.getHoldCount();
  }

  //---------------------------------------------------------------------------
  // Special FBO hook
  //

  /**
   * Tracks {@link GL#GL_FRAMEBUFFER}, {@link GL2GL3#GL_DRAW_FRAMEBUFFER} and {@link GL2GL3#GL_READ_FRAMEBUFFER}
   * to be returned via {@link #getBoundFramebuffer(int)}.
   *
   * <p>Invoked by {@link GL#glBindFramebuffer(int, int)}. </p>
   *
   * <p>Assumes valid <code>framebufferName</code> range of [0..{@link Integer#MAX_VALUE}]</p>
   *
   * <p>Does not throw an exception if <code>target</code> is unknown or <code>framebufferName</code> invalid.</p>
   */
  public final void setBoundFramebuffer(int target, int framebufferName) {
      if(0 > framebufferName) {
          return; // ignore invalid name
      }
      switch(target) {
          case GL.GL_FRAMEBUFFER:
              boundFBOTarget[0] = framebufferName; // draw
              boundFBOTarget[1] = framebufferName; // read
              break;
          case GL2GL3.GL_DRAW_FRAMEBUFFER:
              boundFBOTarget[0] = framebufferName; // draw
              break;
          case GL2GL3.GL_READ_FRAMEBUFFER:
              boundFBOTarget[1] = framebufferName; // read
              break;
          default: // ignore untracked target
      }
  }
  @Override
  public final int getBoundFramebuffer(int target) {
      switch(target) {
          case GL.GL_FRAMEBUFFER:
          case GL2GL3.GL_DRAW_FRAMEBUFFER:
              return boundFBOTarget[0]; // draw
          case GL2GL3.GL_READ_FRAMEBUFFER:
              return boundFBOTarget[1]; // read
          default:
              throw new InternalError("Invalid FBO target name: "+toHexString(target));
      }
  }

  @Override
  public final int getDefaultDrawFramebuffer() { return drawable.getDefaultDrawFramebuffer(); }
  @Override
  public final int getDefaultReadFramebuffer() { return drawable.getDefaultReadFramebuffer(); }
  @Override
  public final int getDefaultReadBuffer() { return drawable.getDefaultReadBuffer(gl); }

  //---------------------------------------------------------------------------
  // GL_ARB_debug_output, GL_AMD_debug_output helpers
  //

  @Override
  public final String getGLDebugMessageExtension() {
      return glDebugHandler.getExtension();
  }

  @Override
  public final boolean isGLDebugMessageEnabled() {
      return glDebugHandler.isEnabled();
  }

  @Override
  public final int getContextCreationFlags() {
      return additionalCtxCreationFlags;
  }

  @Override
  public final void setContextCreationFlags(int flags) {
      if(!isCreated()) {
          additionalCtxCreationFlags = flags & GLContext.CTX_OPTION_DEBUG;
      }
  }

  @Override
  public final boolean isGLDebugSynchronous() { return glDebugHandler.isSynchronous(); }

  @Override
  public final void setGLDebugSynchronous(boolean synchronous) {
      glDebugHandler.setSynchronous(synchronous);
  }

  @Override
  public final void enableGLDebugMessage(boolean enable) throws GLException {
      if(!isCreated()) {
          if(enable) {
              additionalCtxCreationFlags |=  GLContext.CTX_OPTION_DEBUG;
          } else {
              additionalCtxCreationFlags &= ~GLContext.CTX_OPTION_DEBUG;
          }
      } else if(0 != (additionalCtxCreationFlags & GLContext.CTX_OPTION_DEBUG) &&
                null != getGLDebugMessageExtension()) {
          glDebugHandler.enable(enable);
      }
  }

  @Override
  public final void addGLDebugListener(GLDebugListener listener) {
      glDebugHandler.addListener(listener);
  }

  @Override
  public final void removeGLDebugListener(GLDebugListener listener) {
      glDebugHandler.removeListener(listener);
  }

  @Override
  public final void glDebugMessageControl(int source, int type, int severity, int count, IntBuffer ids, boolean enabled) {
      if(glDebugHandler.isExtensionARB()) {
          gl.getGL2GL3().glDebugMessageControl(source, type, severity, count, ids, enabled);
      } else if(glDebugHandler.isExtensionAMD()) {
          gl.getGL2GL3().glDebugMessageEnableAMD(GLDebugMessage.translateARB2AMDCategory(source, type), severity, count, ids, enabled);
      }
  }

  @Override
  public final void glDebugMessageControl(int source, int type, int severity, int count, int[] ids, int ids_offset, boolean enabled) {
      if(glDebugHandler.isExtensionARB()) {
          gl.getGL2GL3().glDebugMessageControl(source, type, severity, count, ids, ids_offset, enabled);
      } else if(glDebugHandler.isExtensionAMD()) {
          gl.getGL2GL3().glDebugMessageEnableAMD(GLDebugMessage.translateARB2AMDCategory(source, type), severity, count, ids, ids_offset, enabled);
      }
  }

  @Override
  public final void glDebugMessageInsert(int source, int type, int id, int severity, String buf) {
      final int len = (null != buf) ? buf.length() : 0;
      if(glDebugHandler.isExtensionARB()) {
          gl.getGL2GL3().glDebugMessageInsert(source, type, id, severity, len, buf);
      } else if(glDebugHandler.isExtensionAMD()) {
          gl.getGL2GL3().glDebugMessageInsertAMD(GLDebugMessage.translateARB2AMDCategory(source, type), severity, id, len, buf);
      }
  }

  /** Internal bootstraping glGetString(GL_RENDERER) */
  private static native String glGetStringInt(int name, long procAddress);

  /** Internal bootstraping glGetIntegerv(..) for version */
  private static native void glGetIntegervInt(int pname, int[] params, int params_offset, long procAddress);
}