/** * 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.FBObject; import com.jogamp.opengl.GL; import com.jogamp.opengl.GLCapabilitiesImmutable; import com.jogamp.opengl.GLContext; import com.jogamp.opengl.GLException; import com.jogamp.opengl.GLFBODrawable; import com.jogamp.opengl.GLProfile; import jogamp.nativewindow.ios.IOSUtil; import jogamp.opengl.GLContextImpl; import jogamp.opengl.GLDrawableImpl; import jogamp.opengl.GLDynamicLookupHelper; import jogamp.opengl.GLFBODrawableImpl; 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; private int lastWidth, lastHeight; 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 { /** FIXME: won't work w/ special drawables (like FBO) - check for CGL mode regressions! * if (getOpenGLMode() != ((IOSEAGLDrawable)drawable).getOpenGLMode()) { setOpenGLMode(((IOSEAGLDrawable)drawable).getOpenGLMode()); } */ 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 { if( drawable.getChosenGLCapabilities().isOnscreen() ) { final int w = drawable.getSurfaceWidth(); final int h = drawable.getSurfaceHeight(); // final boolean sizeChanged = w != lastWidth || h != lastHeight; if(drawable instanceof GLFBODrawable) { final GLFBODrawable fbod = (GLFBODrawable) drawable; final FBObject.Colorbuffer col = fbod.getColorbuffer(GL.GL_FRONT); // FIXME GL_BACK swap .. final int renderbuffer = col.getName(); EAGL.eaglPresentRenderbuffer(contextHandle, renderbuffer); } // TODO: Check for resize ... lastWidth = w; lastHeight = h; } } @Override protected void associateDrawable(final boolean bound) { // context stuff depends on drawable stuff if(bound) { final GLDrawableImpl drawable = getDrawableImpl(); if( drawable instanceof GLFBODrawableImpl ) { final GLFBODrawableImpl fboDrawable = (GLFBODrawableImpl) drawable; fboDrawable.setSwapBufferContext(new SwapBufferContext() { @Override public void swapBuffers(final boolean doubleBuffered) { EAGL.eaglPresentRenderbuffer(contextHandle, GL.GL_RENDERBUFFER); } } ); } // FIXME: Need better way to inject the IOS EAGL Layer into FBObject // FIXME: May want to implement optional injection of a BufferStorage SPI? // FBObject.ColorAttachment.initialize(GL): EAGL.eaglBindDrawableStorageToRenderbuffer(contextHandle, GL.GL_RENDERBUFFER, eaglLayer); final long eaglLayer = IOSUtil.GetCAEAGLLayer(drawable.getNativeSurface().getSurfaceHandle()); System.err.println("EAGL: Ctx attach EAGLLayer 0x"+Long.toHexString(eaglLayer)); attachObject("IOS_EAGL_LAYER", new Long(eaglLayer)); 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 EAGL.eaglBindDrawableStorageToRenderbuffer(contextHandle, GL.GL_RENDERBUFFER, 0); detachObject("IOS_EAGL_LAYER"); } } @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; } /** Only returns a valid UIView. If !UIView, return null and mark isFBO or isSurfaceless. */ private long getUIViewHandle(final boolean[] isFBO, final boolean[] isSurfaceless) { final long uiViewHandle; if(drawable instanceof GLFBODrawableImpl) { uiViewHandle = 0; isFBO[0] = true; isSurfaceless[0] = false; if(DEBUG) { System.err.println("UI viewHandle.1: GLFBODrawableImpl drawable: isFBO "+isFBO[0]+", isSurfaceless "+isSurfaceless[0]+", "+drawable.getClass().getName()+",\n\t"+drawable); } } else { final long drawableHandle = drawable.getHandle(); final boolean isUIView = IOSUtil.isUIView(drawableHandle); final boolean isUIWindow = IOSUtil.isUIWindow(drawableHandle); isFBO[0] = false; isSurfaceless[0] = false; if( isUIView ) { uiViewHandle = drawableHandle; } else if( isUIWindow ) { uiViewHandle = IOSUtil.GetUIView(drawableHandle, true /* only EAGL */); } else if( isSurfaceless() ) { isSurfaceless[0] = true; uiViewHandle = 0; } else { throw new GLException("Drawable's handle neither NSView, NSWindow nor PBuffer: drawableHandle "+toHexString(drawableHandle)+", isNSView "+isUIView+", isNSWindow "+isUIWindow+", isFBO "+isFBO[0]+", "+drawable.getClass().getName()+",\n\t"+drawable); } if(DEBUG) { System.err.println("NS viewHandle.2: drawableHandle "+toHexString(drawableHandle)+" -> nsViewHandle "+toHexString(uiViewHandle)+": isNSView "+isUIView+", isNSWindow "+isUIWindow+", isFBO "+isFBO[0]+", isSurfaceless "+isSurfaceless[0]+", "+drawable.getClass().getName()+",\n\t"+drawable); } } return uiViewHandle; } @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; } }