/** * Copyright 2019 JogAmp Community. All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, are * permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, this list of * conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright notice, this list * of conditions and the following disclaimer in the documentation and/or other materials * provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY JogAmp Community ``AS IS'' AND ANY EXPRESS OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JogAmp Community OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * The views and conclusions contained in the software and documentation are those of the * authors and should not be interpreted as representing official policies, either expressed * or implied, of JogAmp Community. */ package jogamp.opengl.ios.eagl; import java.util.Map; import com.jogamp.nativewindow.AbstractGraphicsConfiguration; import com.jogamp.nativewindow.AbstractGraphicsDevice; import com.jogamp.nativewindow.MutableGraphicsConfiguration; import com.jogamp.nativewindow.OffscreenLayerSurface; import com.jogamp.opengl.GL; import com.jogamp.opengl.GLCapabilitiesImmutable; import com.jogamp.opengl.GLContext; import com.jogamp.opengl.GLException; import com.jogamp.opengl.GLProfile; import jogamp.opengl.GLContextImpl; import jogamp.opengl.GLDrawableFactoryImpl; import jogamp.opengl.GLDrawableImpl; import jogamp.opengl.GLDynamicLookupHelper; import jogamp.opengl.GLFBODrawableImpl; import jogamp.opengl.GLDrawableFactoryImpl.OnscreenFBOColorbufferStorageDefinition; import jogamp.opengl.GLFBODrawableImpl.SwapBufferContext; import jogamp.opengl.DummyGLExtProcAddressTable; import jogamp.opengl.ios.eagl.IOSEAGLDrawable.GLBackendType; import com.jogamp.common.os.Platform; import com.jogamp.gluegen.runtime.ProcAddressTable; import com.jogamp.gluegen.runtime.opengl.GLProcAddressResolver; import com.jogamp.opengl.GLRendererQuirks; public class IOSEAGLContext extends GLContextImpl { // Abstract interface for implementation of this context protected interface GLBackendImpl { /** Indicating CALayer, i.e. onscreen rendering using offscreen layer. */ boolean isUsingCAEAGLLayer(); long create(long share, int ctp, int major, int minor); boolean destroy(long ctx); void associateDrawable(boolean bound); boolean makeCurrent(long ctx); boolean release(long ctx); } static boolean isGLProfileSupported(final int ctp, final int major, final int minor) { if( 0 == ( CTX_PROFILE_ES & ctp ) ) { // only ES profiles supported return false; } return true; } static int GLProfile2EAGLProfileValue(final int ctp, final int major, final int minor) { if(!isGLProfileSupported(ctp, major, minor)) { throw new GLException("OpenGL profile not supported.0: "+getGLVersion(major, minor, ctp, "@GLProfile2EAGLProfileValue")); } switch( major ) { case 1: return EAGL.kEAGLRenderingAPIOpenGLES1; case 2: return EAGL.kEAGLRenderingAPIOpenGLES2; case 3: return EAGL.kEAGLRenderingAPIOpenGLES3; } throw new GLException("OpenGL profile not supported.1: "+getGLVersion(major, minor, ctp, "@GLProfile2EAGLProfileValue")); } private boolean haveSetOpenGLMode = false; private GLBackendType openGLMode = GLBackendType.CAEAGL_LAYER; // Implementation object (either NSOpenGL-based or CGL-based) protected GLBackendImpl impl; // CGL extension functions. private DummyGLExtProcAddressTable cglExtProcAddressTable; protected IOSEAGLContext(final GLDrawableImpl drawable, final GLContext shareWith) { super(drawable, shareWith); initOpenGLImpl(getOpenGLMode()); } @Override protected void resetStates(final boolean isInit) { // no inner state _cglExt = null; super.resetStates(isInit); } @Override public Object getPlatformGLExtensions() { return null; } @Override public final ProcAddressTable getPlatformExtProcAddressTable() { return getCGLExtProcAddressTable(); } public final DummyGLExtProcAddressTable getCGLExtProcAddressTable() { return cglExtProcAddressTable; } @Override protected Map getFunctionNameMap() { return null; } @Override protected Map getExtensionNameMap() { return null; } @Override protected long createContextARBImpl(final long share, final boolean direct, final int ctp, final int major, final int minor) { if(!isGLProfileSupported(ctp, major, minor)) { if(DEBUG) { System.err.println(getThreadName() + ": createContextARBImpl: Not supported "+getGLVersion(major, minor, ctp, "@creation on iOS "+Platform.getOSVersionNumber())); } return 0; } // Will throw exception upon error long ctx = impl.create(share, ctp, major, minor); if(0 != ctx) { if (!impl.makeCurrent(ctx)) { if(DEBUG) { System.err.println(getThreadName() + ": createContextARB couldn't make current "+getGLVersion(major, minor, ctp, "@creation")); } impl.release(ctx); impl.destroy(ctx); ctx = 0; } else if(DEBUG) { System.err.println(getThreadName() + ": createContextARBImpl: OK "+getGLVersion(major, minor, ctp, "@creation")+", share "+share+", direct "+direct+" on iOS "+Platform.getOSVersionNumber()); } } else if(DEBUG) { System.err.println(getThreadName() + ": createContextARBImpl: NO "+getGLVersion(major, minor, ctp, "@creation on iOS "+Platform.getOSVersionNumber())); } return ctx; } @Override protected void destroyContextARBImpl(final long _context) { impl.release(_context); impl.destroy(_context); } @Override public final boolean isGLReadDrawableAvailable() { return false; } @Override protected boolean createImpl(final long shareWithHandle) throws GLException { final MutableGraphicsConfiguration config = (MutableGraphicsConfiguration) drawable.getNativeSurface().getGraphicsConfiguration(); final AbstractGraphicsDevice device = config.getScreen().getDevice(); final GLCapabilitiesImmutable glCaps = (GLCapabilitiesImmutable) config.getChosenCapabilities(); final GLProfile glp = glCaps.getGLProfile(); final boolean createContextARBAvailable = isCreateContextARBAvail(device); if(DEBUG) { System.err.println(getThreadName() + ": IOSEAGLContext.createImpl: START "+glCaps+", share "+toHexString(shareWithHandle)); System.err.println(getThreadName() + ": Use ARB[avail["+getCreateContextARBAvailStr(device)+ "] -> "+createContextARBAvailable+"]]"); } if( !glp.isGLES() ) { throw new GLException("Desktop OpenGL profile not supported on iOS "+Platform.getOSVersionNumber()+": "+glp); } contextHandle = createContextARB(shareWithHandle, true); return 0 != contextHandle; } @Override protected void makeCurrentImpl() throws GLException { if ( !impl.makeCurrent(contextHandle) ) { throw new GLException("Error making Context current: "+this); } drawableUpdatedNotify(); } @Override protected void releaseImpl() throws GLException { if (!impl.release(contextHandle)) { throw new GLException("Error releasing OpenGL Context: "+this); } } @Override protected void destroyImpl() throws GLException { if(!impl.destroy(contextHandle)) { throw new GLException("Error destroying OpenGL Context: "+this); } } @Override protected void drawableUpdatedNotify() throws GLException { // NOTE to resize: GLFBODrawableImpl.resetSize(GL) called from many // high level instances in the GLDrawable* space, // e.g. GLAutoDrawableBase's defaultWindowResizedOp(..) } @Override protected void associateDrawable(final boolean bound) { // context stuff depends on drawable stuff final GLFBODrawableImpl fboDrawable; final boolean taggedOnscreenFBOEAGLLayer; { final GLDrawableImpl drawable = getDrawableImpl(); final GLDrawableFactoryImpl factory = drawable.getFactoryImpl(); final IOSEAGLDrawableFactory iosFactory = (factory instanceof IOSEAGLDrawableFactory) ? (IOSEAGLDrawableFactory) factory : null; final OnscreenFBOColorbufferStorageDefinition onscreenFBOColorbufStorageDef = (null != iosFactory) ? iosFactory.getOnscreenFBOColorbufStorageDef() : null; fboDrawable = (drawable instanceof GLFBODrawableImpl) ? (GLFBODrawableImpl)drawable : null; taggedOnscreenFBOEAGLLayer = (null != fboDrawable && null != onscreenFBOColorbufStorageDef) ? fboDrawable.hasColorRenderbufferStorageDef(onscreenFBOColorbufStorageDef) : false; } if( DEBUG ) { System.err.println(getThreadName() + ": IOSEAGLContext.associateDrawable(bound "+bound+"): taggedOnscreenFBOEAGLLayer "+taggedOnscreenFBOEAGLLayer+ ", hasFBODrawable "+(null != fboDrawable)+", drawable: "+getDrawableImpl().getClass().getName()); } if(bound) { if( taggedOnscreenFBOEAGLLayer ) { // Done in GLDrawableFactory.createGDrawable(..) for onscreen drawables: // fboDrawable.setColorRenderbufferStorageDef(iosFactory.getOnscreenFBOColorbufStorageDef()); fboDrawable.setSwapBufferContext(new SwapBufferContext() { @Override public void swapBuffers(final boolean doubleBuffered) { EAGL.eaglPresentRenderbuffer(contextHandle, GL.GL_RENDERBUFFER); } } ); } super.associateDrawable(true); // 1) init drawable stuff (FBO init, ..) impl.associateDrawable(true); // 2) init context stuff } else { impl.associateDrawable(false); // 1) free context stuff super.associateDrawable(false); // 2) free drawable stuff if( taggedOnscreenFBOEAGLLayer ) { EAGL.eaglBindDrawableStorageToRenderbuffer(contextHandle, GL.GL_RENDERBUFFER, 0); } } } @Override protected void copyImpl(final GLContext source, final int mask) throws GLException { throw new GLException("copyImpl n/a: "+this); } /** * {@inheritDoc} *

* Ignoring {@code contextFQN}, using {@code iOS}-{@link AbstractGraphicsDevice#getUniqueID()}. *

*/ @Override protected final void updateGLXProcAddressTable(final String contextFQN, final GLDynamicLookupHelper dlh) { if( null == dlh ) { throw new GLException("No GLDynamicLookupHelper for "+this); } final AbstractGraphicsConfiguration aconfig = drawable.getNativeSurface().getGraphicsConfiguration(); final AbstractGraphicsDevice adevice = aconfig.getScreen().getDevice(); final String key = "iOS-"+adevice.getUniqueID(); if (DEBUG) { System.err.println(getThreadName() + ": Initializing EAGL extension address table: "+key); } ProcAddressTable table = null; synchronized(mappedContextTypeObjectLock) { table = mappedGLXProcAddress.get( key ); } if(null != table) { cglExtProcAddressTable = (DummyGLExtProcAddressTable) table; if(DEBUG) { System.err.println(getThreadName() + ": GLContext CGL ProcAddressTable reusing key("+key+") -> "+toHexString(table.hashCode())); } } else { cglExtProcAddressTable = new DummyGLExtProcAddressTable(new GLProcAddressResolver()); resetProcAddressTable(getCGLExtProcAddressTable(), dlh); synchronized(mappedContextTypeObjectLock) { mappedGLXProcAddress.put(key, getCGLExtProcAddressTable()); if(DEBUG) { System.err.println(getThreadName() + ": GLContext CGL ProcAddressTable mapping key("+key+") -> "+toHexString(getCGLExtProcAddressTable().hashCode())); } } } } @Override protected final StringBuilder getPlatformExtensionsStringImpl() { return new StringBuilder(); } // Support for "mode switching" as described in IOSEAGLDrawable public void setOpenGLMode(final GLBackendType mode) { if (mode == openGLMode) { return; } if (haveSetOpenGLMode) { throw new GLException("Can't switch between using EAGL and ... more than once"); } destroyImpl(); ((IOSEAGLDrawable)drawable).setOpenGLMode(mode); if (DEBUG) { System.err.println("IOSEAGLContext: Switching context mode " + openGLMode + " -> " + mode); } initOpenGLImpl(mode); openGLMode = mode; haveSetOpenGLMode = true; } public final GLBackendType getOpenGLMode() { return openGLMode; } protected void initOpenGLImpl(final GLBackendType backend) { switch (backend) { case CAEAGL_LAYER: impl = new CAEAGLLayerImpl(); break; default: throw new InternalError("Illegal implementation mode " + backend); } } @Override public String toString() { final StringBuilder sb = new StringBuilder(); sb.append(getClass().getSimpleName()); sb.append(" ["); super.append(sb); sb.append("] "); return sb.toString(); } class CAEAGLLayerImpl implements GLBackendImpl { private final OffscreenLayerSurface backingLayerHost = null; @Override public boolean isUsingCAEAGLLayer() { return null != backingLayerHost; } @Override public long create(final long share, final int ctp, final int major, final int minor) { long ctx = 0; final MutableGraphicsConfiguration config = (MutableGraphicsConfiguration) drawable.getNativeSurface().getGraphicsConfiguration(); final GLCapabilitiesImmutable chosenCaps = (GLCapabilitiesImmutable)config.getChosenCapabilities(); // Create new context if (DEBUG) { System.err.println("Share context for EAGL-based context is " + toHexString(share)); } final boolean isFBO = drawable instanceof GLFBODrawableImpl; final int api = GLProfile2EAGLProfileValue(ctp, major, minor); if( 0 != share ) { ctx = EAGL.eaglCreateContextShared(api, EAGL.eaglGetSharegroup(share)); } else { ctx = EAGL.eaglCreateContext(api); } if (0 != ctx) { final GLCapabilitiesImmutable fixedCaps; if( isFBO ) { fixedCaps = chosenCaps; } else { if( DEBUG ) { System.err.println("Warning: CAEAGLLayer w/ non FBO caps"); } fixedCaps = chosenCaps; } if(DEBUG) { System.err.println("NS create backingLayerHost: "+backingLayerHost); System.err.println("NS create share: "+share); System.err.println("NS create drawable type: "+drawable.getClass().getName()); System.err.println("NS create chosenCaps: "+chosenCaps); System.err.println("NS create fixedCaps: "+fixedCaps); System.err.println("NS create drawable native-handle: "+toHexString(drawable.getHandle())); System.err.println("NS create surface native-handle: "+toHexString(drawable.getNativeSurface().getSurfaceHandle())); // Thread.dumpStack(); } config.setChosenCapabilities(fixedCaps); if(DEBUG) { System.err.println("EAGL create fixedCaps: "+fixedCaps); } } return ctx; } @Override public boolean destroy(final long ctx) { return EAGL.eaglDeleteContext(ctx, true /* releaseOnMainThread */); } @Override public void associateDrawable(final boolean bound) { } @Override public boolean makeCurrent(final long ctx) { return EAGL.eaglMakeCurrentContext(ctx); } @Override public boolean release(final long ctx) { try { if( hasRendererQuirk(GLRendererQuirks.GLFlushBeforeRelease) && null != IOSEAGLContext.this.getGLProcAddressTable() ) { gl.glFlush(); } } catch (final GLException gle) { if(DEBUG) { System.err.println("IOSEAGLContext.CGLImpl.release: INFO: glFlush() caught exception:"); gle.printStackTrace(); } } return EAGL.eaglMakeCurrentContext(0); } } @Override protected Integer setSwapIntervalImpl2(final int interval) { // TODO return null; } }